feat(ui): add combined bags toggle

This commit is contained in:
Kelsi
2026-07-12 07:52:45 -07:00
parent 03b2f6aa66
commit 46a7e97f95
5 changed files with 104 additions and 27 deletions

View File

@@ -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,

View File

@@ -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_; }

View File

@@ -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<game::EquipSlot>(static_cast<int>(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,

View File

@@ -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_);

View File

@@ -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<unsigned long long>(gold),
static_cast<unsigned long long>(silver),
static_cast<unsigned long long>(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();
}