From 46a7e97f95d5f06fc7ba5155ad4e8bd044ae2a07 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sun, 12 Jul 2026 07:52:45 -0700 Subject: [PATCH] feat(ui): add combined bags toggle --- include/ui/action_bar_panel.hpp | 2 +- include/ui/inventory_screen.hpp | 2 + src/ui/action_bar_panel.cpp | 26 +++++++-- src/ui/game_screen.cpp | 3 +- src/ui/inventory_screen.cpp | 98 ++++++++++++++++++++++++++------- 5 files changed, 104 insertions(+), 27 deletions(-) diff --git a/include/ui/action_bar_panel.hpp b/include/ui/action_bar_panel.hpp index 60ee6384..e1a0b507 100644 --- a/include/ui/action_bar_panel.hpp +++ b/include/ui/action_bar_panel.hpp @@ -48,7 +48,7 @@ public: SettingsPanel& settingsPanel, SpellbookScreen& spellbookScreen, SpellIconFn getSpellIcon); - void renderBagBar(game::GameHandler& gameHandler, + bool renderBagBar(game::GameHandler& gameHandler, SettingsPanel& settingsPanel, InventoryScreen& inventoryScreen); void renderXpBar(game::GameHandler& gameHandler, diff --git a/include/ui/inventory_screen.hpp b/include/ui/inventory_screen.hpp index a85bb7ce..4a54822a 100644 --- a/include/ui/inventory_screen.hpp +++ b/include/ui/inventory_screen.hpp @@ -36,6 +36,8 @@ public: void toggleBag(int idx); void openAllBags(); void closeAllBags(); + /// Toggle between independently positioned bag windows and one continuous grid. + void toggleCombinedBags(); void setSeparateBags(bool sep) { separateBags_ = sep; } bool isSeparateBags() const { return separateBags_; } void toggleCompactBags() { compactBags_ = !compactBags_; } diff --git a/src/ui/action_bar_panel.cpp b/src/ui/action_bar_panel.cpp index 061be57a..8fe319ef 100644 --- a/src/ui/action_bar_panel.cpp +++ b/src/ui/action_bar_panel.cpp @@ -1248,20 +1248,21 @@ void ActionBarPanel::renderStanceBar(game::GameHandler& gameHandler, ImGui::PopStyleVar(4); } -void ActionBarPanel::renderBagBar(game::GameHandler& gameHandler, - SettingsPanel& /*settingsPanel*/, +bool ActionBarPanel::renderBagBar(game::GameHandler& gameHandler, + SettingsPanel& settingsPanel, InventoryScreen& inventoryScreen) { ImVec2 displaySize = ImGui::GetIO().DisplaySize; float screenW = displaySize.x > 0.0f ? displaySize.x : 1280.0f; float screenH = displaySize.y > 0.0f ? displaySize.y : 720.0f; auto* assetMgr = services_.assetManager; + bool settingChanged = false; float slotSize = 42.0f; float spacing = 4.0f; float padding = 6.0f; - // 5 slots: backpack + 4 bags - float barW = 5 * slotSize + 4 * spacing + padding * 2; + // Mode toggle + backpack + 4 bags + float barW = 6 * slotSize + 5 * spacing + padding * 2; float barH = slotSize + padding * 2; // Position in bottom right corner @@ -1284,6 +1285,20 @@ void ActionBarPanel::renderBagBar(game::GameHandler& gameHandler, if (ImGui::Begin("##BagBar", nullptr, flags)) { auto& inv = gameHandler.getInventory(); + // Keep this next to the bags so changing layouts never requires a trip + // through settings. The saved setting remains the single source of truth. + const bool combined = !inventoryScreen.isSeparateBags(); + if (ImGui::Button(combined ? "Split" : "All", ImVec2(slotSize, slotSize))) { + inventoryScreen.toggleCombinedBags(); + settingsPanel.pendingSeparateBags = inventoryScreen.isSeparateBags(); + settingChanged = true; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip(combined + ? "Split into separate bag windows" + : "Combine all bag slots into one window"); + } + // Load backpack icon if needed if (!backpackIconTexture_ && assetMgr && assetMgr->isInitialized()) { auto blpData = assetMgr->readFile("Interface\\Buttons\\Button-Backpack-Up.blp"); @@ -1303,7 +1318,7 @@ void ActionBarPanel::renderBagBar(game::GameHandler& gameHandler, // Slots 1-4: Bag slots (leftmost) for (int i = 0; i < 4; ++i) { - if (i > 0) ImGui::SameLine(0, spacing); + ImGui::SameLine(0, spacing); ImGui::PushID(i + 1); game::EquipSlot bagSlot = static_cast(static_cast(game::EquipSlot::BAG1) + i); @@ -1524,6 +1539,7 @@ void ActionBarPanel::renderBagBar(game::GameHandler& gameHandler, fg->AddRect(p0, p1, IM_COL32(200, 200, 200, 255), 0.0f, 0, 2.0f); } } + return settingChanged; } void ActionBarPanel::renderXpBar(game::GameHandler& gameHandler, diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index de158cac..e45c82ec 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -414,7 +414,8 @@ void GameScreen::render(game::GameHandler& gameHandler) { [this](uint32_t id, pipeline::AssetManager* am) { return getSpellIcon(id, am); }); actionBarPanel_.renderStanceBar(gameHandler, settingsPanel_, spellbookScreen, [this](uint32_t id, pipeline::AssetManager* am) { return getSpellIcon(id, am); }); - actionBarPanel_.renderBagBar(gameHandler, settingsPanel_, inventoryScreen); + if (actionBarPanel_.renderBagBar(gameHandler, settingsPanel_, inventoryScreen)) + saveSettings(); renderMicroMenu(gameHandler); actionBarPanel_.renderXpBar(gameHandler, settingsPanel_); actionBarPanel_.renderRepBar(gameHandler, settingsPanel_); diff --git a/src/ui/inventory_screen.cpp b/src/ui/inventory_screen.cpp index 42e47380..56518fe9 100644 --- a/src/ui/inventory_screen.cpp +++ b/src/ui/inventory_screen.cpp @@ -815,6 +815,20 @@ void InventoryScreen::closeAllBags() { for (auto& b : bagOpen_) b = false; } +void InventoryScreen::toggleCombinedBags() { + if (separateBags_) { + // Consolidating is an explicit request to see the inventory, even when + // all of the individual windows happened to be closed. + separateBags_ = false; + open = true; + } else { + // Restore every physical bag as a visible, independently movable window. + separateBags_ = true; + openAllBags(); + open = true; + } +} + bool InventoryScreen::bagHasAnyItems(const game::Inventory& inventory, int bagIndex) const { int bagSize = inventory.getBagSize(bagIndex); if (bagSize <= 0) return false; @@ -1006,15 +1020,33 @@ void InventoryScreen::renderAggregateBags(game::Inventory& inventory, uint64_t m constexpr float slotSize = 40.0f; constexpr int columns = 6; - int rows = (inventory.getBackpackSize() + columns - 1) / columns; - float bagContentH = rows * (slotSize + 4.0f) + 40.0f; - + int totalSlots = inventory.getBackpackSize(); + int usedSlots = 0; + for (int slot = 0; slot < inventory.getBackpackSize(); ++slot) + usedSlots += !inventory.getBackpackSlot(slot).empty(); for (int bag = 0; bag < game::Inventory::NUM_BAG_SLOTS; bag++) { int bagSize = inventory.getBagSize(bag); if (bagSize <= 0) continue; - if (compactBags_ && !bagHasAnyItems(inventory, bag)) continue; - int bagRows = (bagSize + columns - 1) / columns; - bagContentH += bagRows * (slotSize + 4.0f) + 30.0f; + totalSlots += bagSize; + for (int slot = 0; slot < bagSize; ++slot) + usedSlots += !inventory.getBagSlot(bag, slot).empty(); + } + + int rows = (totalSlots + columns - 1) / columns; + float bagContentH = rows * (slotSize + 4.0f) + 40.0f; + int visibleKeySlots = 0; + if (showKeyring_) { + constexpr int keyColumns = 8; + int lastOccupied = -1; + for (int slot = inventory.getKeyringSize() - 1; slot >= 0; --slot) { + if (!inventory.getKeyringSlot(slot).empty()) { lastOccupied = slot; break; } + } + visibleKeySlots = lastOccupied < 0 ? 0 : ((lastOccupied / keyColumns) + 1) * keyColumns; + if (visibleKeySlots > 0) { + constexpr float keySlotSize = 24.0f; + const int keyRows = (visibleKeySlots + keyColumns - 1) / keyColumns; + bagContentH += 30.0f + keyRows * (keySlotSize + 4.0f); + } } float windowW = columns * (slotSize + 4.0f) + 30.0f; @@ -1029,7 +1061,9 @@ void InventoryScreen::renderAggregateBags(game::Inventory& inventory, uint64_t m ImGuiWindowFlags flags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize; if (holdingItem || pickupPending_) flags |= ImGuiWindowFlags_NoMove; - bool windowVisible = ImGui::Begin("Bags", &open, flags); + char windowTitle[64]; + snprintf(windowTitle, sizeof(windowTitle), "All Bags (%d/%d)###Bags", usedSlots, totalSlots); + bool windowVisible = ImGui::Begin(windowTitle, &open, flags); if (!windowVisible) { ImGui::End(); return; @@ -1043,7 +1077,43 @@ void InventoryScreen::renderAggregateBags(game::Inventory& inventory, uint64_t m ImGui::SetWindowPos(ImVec2(posX, posY)); } - renderBackpackPanel(inventory, compactBags_); + // Draw one uninterrupted grid while retaining each slot's real container + // and index for pickup, use, split, destroy, and server swap operations. + int gridIndex = 0; + auto renderCombinedSlot = [&](const game::ItemSlot& slot, int backpackIndex, + int bagIndex, int bagSlotIndex) { + if (gridIndex % columns != 0) ImGui::SameLine(); + ImGui::PushID(gridIndex); + renderItemSlot(inventory, slot, slotSize, nullptr, + SlotKind::BACKPACK, backpackIndex, game::EquipSlot::NUM_SLOTS, + bagIndex, bagSlotIndex); + ImGui::PopID(); + ++gridIndex; + }; + + for (int slot = 0; slot < inventory.getBackpackSize(); ++slot) + renderCombinedSlot(inventory.getBackpackSlot(slot), slot, -1, -1); + for (int bag = 0; bag < game::Inventory::NUM_BAG_SLOTS; ++bag) { + const int bagSize = inventory.getBagSize(bag); + if (bagSize <= 0) continue; + for (int slot = 0; slot < bagSize; ++slot) + renderCombinedSlot(inventory.getBagSlot(bag, slot), -1, bag, slot); + } + + if (visibleKeySlots > 0) { + constexpr float keySlotSize = 24.0f; + constexpr int keyColumns = 8; + ImGui::Spacing(); + ImGui::Separator(); + ImGui::TextColored(ui::colors::kDarkYellow, "Keyring"); + for (int slot = 0; slot < visibleKeySlots; ++slot) { + if (slot % keyColumns != 0) ImGui::SameLine(); + ImGui::PushID(10000 + slot); + renderItemSlot(inventory, inventory.getKeyringSlot(slot), keySlotSize, nullptr, + SlotKind::BACKPACK, -1, game::EquipSlot::NUM_SLOTS); + ImGui::PopID(); + } + } ImGui::Spacing(); uint64_t gold = moneyCopper / 10000; @@ -1053,18 +1123,6 @@ void InventoryScreen::renderAggregateBags(game::Inventory& inventory, uint64_t m static_cast(gold), static_cast(silver), static_cast(copper)); - ImGui::SameLine(); - const char* collapseLabel = compactBags_ ? "Expand Empty" : "Collapse Empty"; - const float btnW = 92.0f; - const float rightMargin = 8.0f; - float rightX = ImGui::GetWindowContentRegionMax().x - btnW - rightMargin; - if (rightX > ImGui::GetCursorPosX()) ImGui::SetCursorPosX(rightX); - if (ImGui::SmallButton(collapseLabel)) { - compactBags_ = !compactBags_; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Toggle empty bag section visibility"); - } ImGui::End(); }