From 37d6298b7219554e22c04dd4c8c82d2d064c647f Mon Sep 17 00:00:00 2001 From: Kelsi Date: Fri, 6 Feb 2026 20:27:01 -0800 Subject: [PATCH] Fix action bar click-to-cast and add spellbook drag-and-drop Left-click on action bar slots now casts spells/uses items instead of starting a drag. Right-click-drag rearranges slots. Spells can be dragged from the spellbook directly onto the action bar, replacing the old "Assign to" button row. --- include/ui/spellbook_screen.hpp | 11 +++- src/ui/game_screen.cpp | 21 ++++--- src/ui/spellbook_screen.cpp | 104 +++++++++++++++----------------- 3 files changed, 73 insertions(+), 63 deletions(-) diff --git a/include/ui/spellbook_screen.hpp b/include/ui/spellbook_screen.hpp index e2eaa5ca..28b20db7 100644 --- a/include/ui/spellbook_screen.hpp +++ b/include/ui/spellbook_screen.hpp @@ -31,6 +31,11 @@ public: void toggle() { open = !open; } void setOpen(bool o) { open = o; } + // Drag-and-drop state for action bar assignment + bool isDraggingSpell() const { return draggingSpell_; } + uint32_t getDragSpellId() const { return dragSpellId_; } + void consumeDragSpell() { draggingSpell_ = false; dragSpellId_ = 0; dragSpellIconTex_ = 0; } + private: bool open = false; bool pKeyWasDown = false; @@ -54,8 +59,10 @@ private: // Tab state SpellTab currentTab = SpellTab::GENERAL; - // Action bar assignment - int assigningSlot = -1; + // Drag-and-drop from spellbook to action bar + bool draggingSpell_ = false; + uint32_t dragSpellId_ = 0; + GLuint dragSpellIconTex_ = 0; void loadSpellDBC(pipeline::AssetManager* assetManager); void loadSpellIconDBC(pipeline::AssetManager* assetManager); diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 001470bb..b5771aeb 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -1509,15 +1509,21 @@ void GameScreen::renderActionBar(game::GameHandler& gameHandler) { ImGui::PopStyleColor(); } + bool rightClicked = ImGui::IsItemClicked(ImGuiMouseButton_Right); + // Drop held item from inventory onto action bar if (clicked && inventoryScreen.isHoldingItem()) { const auto& held = inventoryScreen.getHeldItem(); gameHandler.setActionBarSlot(i, game::ActionBarSlot::ITEM, held.itemId); inventoryScreen.returnHeldItem(gameHandler.getInventory()); + } else if (clicked && spellbookScreen.isDraggingSpell()) { + // Drop dragged spell from spellbook onto this slot + gameHandler.setActionBarSlot(i, game::ActionBarSlot::SPELL, + spellbookScreen.getDragSpellId()); + spellbookScreen.consumeDragSpell(); } else if (clicked && actionBarDragSlot_ >= 0) { // Dropping a dragged action bar slot onto another slot - swap or place if (i != actionBarDragSlot_) { - // Swap the two slots const auto& dragSrc = bar[actionBarDragSlot_]; auto srcType = dragSrc.type; auto srcId = dragSrc.id; @@ -1527,16 +1533,17 @@ void GameScreen::renderActionBar(game::GameHandler& gameHandler) { actionBarDragSlot_ = -1; actionBarDragIcon_ = 0; } else if (clicked && !slot.isEmpty()) { - // Pick up this action bar slot for dragging - actionBarDragSlot_ = i; - actionBarDragIcon_ = iconTex; - } else if (clicked) { + // Left-click on non-empty slot: cast spell or use item if (slot.type == game::ActionBarSlot::SPELL && slot.isReady()) { uint64_t target = gameHandler.hasTarget() ? gameHandler.getTargetGuid() : 0; gameHandler.castSpell(slot.id, target); } else if (slot.type == game::ActionBarSlot::ITEM && slot.id != 0) { gameHandler.useItemById(slot.id); } + } else if (rightClicked && !slot.isEmpty()) { + // Right-click on non-empty slot: pick up for dragging + actionBarDragSlot_ = i; + actionBarDragIcon_ = iconTex; } // Tooltip @@ -1604,8 +1611,8 @@ void GameScreen::renderActionBar(game::GameHandler& gameHandler) { IM_COL32(80, 80, 120, 180)); } - // On mouse release, check if outside the action bar area - if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { + // On right mouse release, check if outside the action bar area + if (ImGui::IsMouseReleased(ImGuiMouseButton_Right)) { bool insideBar = (mousePos.x >= barX && mousePos.x <= barX + barW && mousePos.y >= barY && mousePos.y <= barY + barH); if (!insideBar) { diff --git a/src/ui/spellbook_screen.cpp b/src/ui/spellbook_screen.cpp index 851a6a34..ca363039 100644 --- a/src/ui/spellbook_screen.cpp +++ b/src/ui/spellbook_screen.cpp @@ -204,22 +204,12 @@ void SpellbookScreen::render(game::GameHandler& gameHandler, pipeline::AssetMana if (ImGui::BeginTabItem(label)) { currentTab = tab; - // Action bar assignment mode indicator - if (assigningSlot >= 0) { - ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.3f, 1.0f), - "Click a spell to assign to slot %d", assigningSlot + 1); - if (ImGui::SmallButton("Cancel")) { - assigningSlot = -1; - } - ImGui::Separator(); - } - if (spellList.empty()) { ImGui::TextDisabled("No spells in this category."); } // Spell list with icons - ImGui::BeginChild("SpellList", ImVec2(0, -60), true); + ImGui::BeginChild("SpellList", ImVec2(0, 0), true); float iconSize = 32.0f; bool isPassiveTab = (tab == SpellTab::PASSIVE); @@ -274,35 +264,40 @@ void SpellbookScreen::render(game::GameHandler& gameHandler, pipeline::AssetMana ImGui::GetWindowDrawList()->AddRectFilled( rowMin, rowMax, IM_COL32(255, 255, 255, 20)); - if (ImGui::IsMouseClicked(0)) { - if (assigningSlot >= 0 && !isPassiveTab) { - gameHandler.setActionBarSlot(assigningSlot, - game::ActionBarSlot::SPELL, info->spellId); - assigningSlot = -1; - } + // Left-click-drag to pick up spell for action bar + if (ImGui::IsMouseClicked(0) && !isPassiveTab) { + draggingSpell_ = true; + dragSpellId_ = info->spellId; + dragSpellIconTex_ = iconTex; } if (ImGui::IsMouseDoubleClicked(0) && !isPassiveTab && !onCooldown) { + // Double-click casts (cancel any drag) + draggingSpell_ = false; + dragSpellId_ = 0; + dragSpellIconTex_ = 0; uint64_t target = gameHandler.hasTarget() ? gameHandler.getTargetGuid() : 0; gameHandler.castSpell(info->spellId, target); } - // Tooltip - ImGui::BeginTooltip(); - ImGui::Text("%s", info->name.c_str()); - if (!info->rank.empty()) { - ImGui::TextDisabled("%s", info->rank.c_str()); - } - ImGui::TextDisabled("Spell ID: %u", info->spellId); - if (isPassiveTab) { - ImGui::TextDisabled("Passive"); - } else { - if (!onCooldown) { - ImGui::TextDisabled("Double-click to cast"); + // Tooltip (only when not dragging) + if (!draggingSpell_) { + ImGui::BeginTooltip(); + ImGui::Text("%s", info->name.c_str()); + if (!info->rank.empty()) { + ImGui::TextDisabled("%s", info->rank.c_str()); } - ImGui::TextDisabled("Use buttons below to assign to action bar"); + ImGui::TextDisabled("Spell ID: %u", info->spellId); + if (isPassiveTab) { + ImGui::TextDisabled("Passive"); + } else { + ImGui::TextDisabled("Drag to action bar to assign"); + if (!onCooldown) { + ImGui::TextDisabled("Double-click to cast"); + } + } + ImGui::EndTooltip(); } - ImGui::EndTooltip(); } if (isPassiveTab || onCooldown) { @@ -329,35 +324,36 @@ void SpellbookScreen::render(game::GameHandler& gameHandler, pipeline::AssetMana ImGui::EndTabBar(); } - - // Action bar quick-assign buttons (not for passive tab) - if (currentTab != SpellTab::PASSIVE) { - ImGui::Separator(); - ImGui::Text("Assign to:"); - ImGui::SameLine(); - static const char* slotLabels[] = {"1","2","3","4","5","6","7","8","9","0","-","="}; - for (int i = 0; i < 12; ++i) { - if (i > 0) ImGui::SameLine(0, 2); - ImGui::PushID(100 + i); - bool isAssigning = (assigningSlot == i); - if (isAssigning) { - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8f, 0.6f, 0.2f, 1.0f)); - } - if (ImGui::SmallButton(slotLabels[i])) { - assigningSlot = isAssigning ? -1 : i; - } - if (isAssigning) { - ImGui::PopStyleColor(); - } - ImGui::PopID(); - } - } } ImGui::End(); if (!windowOpen) { open = false; } + + // Render dragged spell icon at cursor + if (draggingSpell_ && dragSpellId_ != 0) { + ImVec2 mousePos = ImGui::GetMousePos(); + float dragSize = 32.0f; + if (dragSpellIconTex_) { + ImGui::GetForegroundDrawList()->AddImage( + (ImTextureID)(uintptr_t)dragSpellIconTex_, + ImVec2(mousePos.x - dragSize * 0.5f, mousePos.y - dragSize * 0.5f), + ImVec2(mousePos.x + dragSize * 0.5f, mousePos.y + dragSize * 0.5f)); + } else { + ImGui::GetForegroundDrawList()->AddRectFilled( + ImVec2(mousePos.x - dragSize * 0.5f, mousePos.y - dragSize * 0.5f), + ImVec2(mousePos.x + dragSize * 0.5f, mousePos.y + dragSize * 0.5f), + IM_COL32(80, 80, 120, 180)); + } + + // Cancel drag on mouse release (action bar consumes it before this if dropped on a slot) + if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { + draggingSpell_ = false; + dragSpellId_ = 0; + dragSpellIconTex_ = 0; + } + } } }} // namespace wowee::ui