From f4ba9796c2af5bc4e9fcf91c8399f4b30a2160d0 Mon Sep 17 00:00:00 2001 From: PanteraPolnocy Date: Wed, 15 Jul 2026 15:51:00 +0200 Subject: [PATCH] FIRE-36831 Hiding / showing custom inventory tabs (filters) Signed-off-by: PanteraPolnocy --- indra/llui/lltabcontainer.cpp | 20 ++ indra/llui/lltabcontainer.h | 1 + .../app_settings/settings_per_account.xml | 2 +- indra/newview/fsinventorycustomtabs.cpp | 280 ++++++++++++++++-- indra/newview/fsinventorycustomtabs.h | 13 + .../xui/en/menu_inventory_custom_tab.xml | 6 + .../newview/skins/default/xui/en/strings.xml | 1 + .../xui/pl/menu_inventory_custom_tab.xml | 1 + .../newview/skins/default/xui/pl/strings.xml | 3 + 9 files changed, 309 insertions(+), 18 deletions(-) diff --git a/indra/llui/lltabcontainer.cpp b/indra/llui/lltabcontainer.cpp index abb853ffd1..79ab398cd0 100644 --- a/indra/llui/lltabcontainer.cpp +++ b/indra/llui/lltabcontainer.cpp @@ -2047,6 +2047,26 @@ S32 LLTabContainer::getTabContainedAtPoint(S32 x, S32 y) const } return -1; } + +void LLTabContainer::setTabButtonVisible(const LLPanel* panel, bool visible) +{ + for (LLTabTuple* tuple : mTabList) + { + if (tuple->mTabPanel == panel) + { + if (tuple->mVisible != visible) + { + tuple->mVisible = visible; + if (tuple->mButton) + { + tuple->mButton->setVisible(visible); + } + updateMaxScrollPos(); + } + break; + } + } +} // void LLTabContainer::onNextBtn( const LLSD& data ) diff --git a/indra/llui/lltabcontainer.h b/indra/llui/lltabcontainer.h index 8fa2b74d6e..8a0813a977 100644 --- a/indra/llui/lltabcontainer.h +++ b/indra/llui/lltabcontainer.h @@ -245,6 +245,7 @@ public: // FIRE-35598: Custom filters in inventory (feature idea: Catznip) S32 getTabContainedAtPoint(S32 x, S32 y) const; void setTabPadding(S32 index, S32 padding); + void setTabButtonVisible(const LLPanel* panel, bool visible); // TabPosition getTabPosition() const { return mTabPosition; } diff --git a/indra/newview/app_settings/settings_per_account.xml b/indra/newview/app_settings/settings_per_account.xml index 2546a5063a..c2f83389fb 100644 --- a/indra/newview/app_settings/settings_per_account.xml +++ b/indra/newview/app_settings/settings_per_account.xml @@ -1401,7 +1401,7 @@ HideFromEditor 1 Comment - User-defined custom inventory filter tabs (array of maps with 'name', 'filter' and 'states' keys) + User-defined custom inventory filter tabs (array of maps with 'name', 'filter', 'states' and 'tabhidden' keys) Persist 1 Type diff --git a/indra/newview/fsinventorycustomtabs.cpp b/indra/newview/fsinventorycustomtabs.cpp index a3031a78d4..17ada047f0 100644 --- a/indra/newview/fsinventorycustomtabs.cpp +++ b/indra/newview/fsinventorycustomtabs.cpp @@ -59,6 +59,10 @@ namespace constexpr char CLOSE_ICON_NAME[] = "Icon_Close_Foreground"; constexpr char CLOSE_ICON_PRESS[] = "Icon_Close_Press"; constexpr char TAB_BUTTON_PREFIX[] = "htab_"; + constexpr char SHOW_HIDDEN_PANEL_NAME[] = "FSInventoryCustomTabShowHidden"; + constexpr char SHOW_HIDDEN_ICON_NAME[] = "Arrow_Down"; + constexpr char HIDDEN_MENU_NAME[] = "fs_inv_hidden_tabs_menu"; + constexpr char TABHIDDEN_KEY[] = "tabhidden"; constexpr S32 CLOSE_ICON_SIZE = 16; constexpr S32 CLOSE_ICON_MARGIN = 4; constexpr S32 CLOSE_BUTTON_RESERVE = CLOSE_ICON_SIZE + CLOSE_ICON_MARGIN + 2; @@ -100,6 +104,7 @@ FSInventoryCustomTabs::FSInventoryCustomTabs(LLPanelMainInventory* parent) } installAddTab(); + installShowHiddenTab(); allInstances().insert(this); } @@ -124,6 +129,10 @@ FSInventoryCustomTabs::~FSInventoryCustomTabs() { mAddClickConnection.disconnect(); } + if (mShowHiddenClickConnection.connected()) + { + mShowHiddenClickConnection.disconnect(); + } if (mTabs && !mPanels.empty()) { save(); @@ -133,6 +142,15 @@ FSInventoryCustomTabs::~FSInventoryCustomTabs() menu->die(); mMenuHandle.markDead(); } + if (auto* hidden_menu = mHiddenMenuHandle.get()) + { + if (gMenuHolder) + { + gMenuHolder->removeChild(hidden_menu); + } + delete hidden_menu; + mHiddenMenuHandle.markDead(); + } } // --------------------------------------------------------------------------- @@ -149,6 +167,7 @@ void FSInventoryCustomTabs::registerCommitCallbacks(LLPanelMainInventory* parent reg.add("FSInventoryCustomTab.Rename", [parent](LLUICtrl*, const LLSD&) { if (auto* self = instanceFor(parent)) self->onRenameClicked(); }); reg.add("FSInventoryCustomTab.Clone", [parent](LLUICtrl*, const LLSD&) { if (auto* self = instanceFor(parent)) self->onCloneClicked(); }); reg.add("FSInventoryCustomTab.Close", [parent](LLUICtrl*, const LLSD&) { if (auto* self = instanceFor(parent)) self->onCloseClicked(); }); + reg.add("FSInventoryCustomTab.Hide", [parent](LLUICtrl*, const LLSD&) { if (auto* self = instanceFor(parent)) self->onHideClicked(); }); } bool FSInventoryCustomTabs::handleRightMouseDown(S32 x, S32 y) @@ -256,6 +275,80 @@ void FSInventoryCustomTabs::onAddClicked() } } +void FSInventoryCustomTabs::onShowHiddenClicked() +{ + if (!mTabs || mHidden.empty()) + { + return; + } + + if (auto* old_menu = mHiddenMenuHandle.get()) + { + if (gMenuHolder) + { + gMenuHolder->removeChild(old_menu); + } + delete old_menu; + mHiddenMenuHandle.markDead(); + } + + LLContextMenu::Params menu_params; + menu_params.name(HIDDEN_MENU_NAME); + menu_params.visible(false); + LLContextMenu* menu = LLUICtrlFactory::create(menu_params); + if (!menu) + { + return; + } + + auto handle = getHandle(); + const S32 count = mTabs->getTabCount(); + for (S32 i = 0; i < count; ++i) + { + auto* inv_panel = dynamic_cast(mTabs->getPanelByIndex(i)); + if (!inv_panel || !mHidden.count(inv_panel)) + { + continue; + } + + std::string label = computeDisplayLabel(inv_panel); + if (label.empty()) + { + label = getDefaultTabName(); + } + + const std::string panel_name = inv_panel->getName(); + LLMenuItemCallGL::Params item_params; + item_params.name(panel_name); + item_params.label(label); + item_params.on_click.function([handle, panel_name](LLUICtrl*, const LLSD&) + { + if (auto* self = handle.get()) + { + if (self->mTabs) + { + self->showTab(dynamic_cast(self->mTabs->getPanelByName(panel_name))); + } + } + }); + menu->addChild(LLUICtrlFactory::create(item_params)); + } + + mHiddenMenuHandle = menu->getHandle(); + if (gMenuHolder) + { + gMenuHolder->addChild(menu); + } + + if (auto* btn = findTabButton(mShowHiddenTabPanel)) + { + S32 screen_x = 0; + S32 screen_y = 0; + btn->localPointToScreen(0, 0, &screen_x, &screen_y); + menu->show(screen_x, screen_y, btn); + } +} + void FSInventoryCustomTabs::noteActivePanel(LLInventoryPanel* panel) { if (mLastActivePanel && mLastActivePanel != panel && isCustomTab(mLastActivePanel)) @@ -266,7 +359,7 @@ void FSInventoryCustomTabs::noteActivePanel(LLInventoryPanel* panel) } save(); } - if (panel && panel != mAddTabPanel && mTabs && mTabs->getIndexForPanel(panel) >= 0) + if (panel && panel != mAddTabPanel && panel != mShowHiddenTabPanel && mTabs && mTabs->getIndexForPanel(panel) >= 0) { mLastActivePanel = panel; } @@ -329,7 +422,12 @@ bool FSInventoryCustomTabs::activePanelIsCustom(const LLPanelMainInventory* pare bool FSInventoryCustomTabs::maybeHandleAddTabSelected(LLPanelMainInventory* parent) { auto* self = instanceFor(parent); - if (!self || !parent->mFilterTabs || !self->isAddTab(parent->mFilterTabs->getCurrentPanel())) + if (!self || !parent->mFilterTabs) + { + return false; + } + const LLPanel* current = parent->mFilterTabs->getCurrentPanel(); + if (!self->isAddTab(current) && !self->isShowHiddenTab(current)) { return false; } @@ -453,6 +551,56 @@ void FSInventoryCustomTabs::installAddTab() } } +void FSInventoryCustomTabs::installShowHiddenTab() +{ + if (!mTabs || mShowHiddenTabPanel) + { + return; + } + + LLInventoryPanel::Params params; + params.name(SHOW_HIDDEN_PANEL_NAME); + params.sort_order_setting(LLInventoryPanel::INHERIT_SORT_ORDER); + params.preinitialize_views(false); + params.follows.flags(FOLLOWS_ALL); + + mShowHiddenTabPanel = LLUICtrlFactory::create(params); + if (!mShowHiddenTabPanel) + { + return; + } + + auto where = LLTabContainer::END; + if (mAddTabPanel) + { + const S32 add_idx = mTabs->getIndexForPanel(mAddTabPanel); + if (add_idx >= 0) + { + where = static_cast(add_idx); + } + } + + mTabs->addTabPanel(LLTabContainer::TabPanelParams().panel(mShowHiddenTabPanel).label(LLStringUtil::null).select_tab(false).insert_at(where)); + + if (auto* btn = findTabButton(mShowHiddenTabPanel)) + { + const S32 height = btn->getRect().getHeight(); + const S32 width = llmax(mTabs->getMinTabWidth() / 2, ADD_TAB_MIN_WIDTH); + btn->reshape(width, height); + btn->setLeftHPad(2); + btn->setRightHPad(2); + btn->setImageOverlay(std::string(SHOW_HIDDEN_ICON_NAME)); + std::string tooltip; + if (LLTrans::findString(tooltip, SHOW_HIDDEN_PANEL_NAME) && !tooltip.empty()) + { + btn->setToolTip(tooltip); + } + mShowHiddenClickConnection = btn->setClickedCallback(boost::bind(&FSInventoryCustomTabs::onShowHiddenClicked, this)); + } + + mTabs->setTabButtonVisible(mShowHiddenTabPanel, false); +} + LLInventoryPanel* FSInventoryCustomTabs::addTab(const std::string& explicit_name, const std::string& filter, bool select) { if (!mTabs || !mParent) @@ -501,12 +649,13 @@ LLInventoryPanel* FSInventoryCustomTabs::addTab(const std::string& explicit_name const std::string& display_label = explicit_name.empty() ? auto_label : explicit_name; auto where = LLTabContainer::END; - if (mAddTabPanel) + LLPanel* anchor = mShowHiddenTabPanel ? static_cast(mShowHiddenTabPanel) : static_cast(mAddTabPanel); + if (anchor) { - const S32 add_idx = mTabs->getIndexForPanel(mAddTabPanel); - if (add_idx >= 0) + const S32 anchor_idx = mTabs->getIndexForPanel(anchor); + if (anchor_idx >= 0) { - where = static_cast(add_idx); + where = static_cast(anchor_idx); } } @@ -665,25 +814,106 @@ void FSInventoryCustomTabs::onCloseConfirmed(const LLSD& notification, const LLS if (mTabs->getCurrentPanel() == to_remove) { - const S32 idx = mTabs->getIndexForPanel(to_remove); - if (idx > 0) - { - mTabs->selectTab(idx - 1); - } - else - { - mTabs->selectFirstTab(); - } + selectNearestVisibleTab(to_remove); } mPanels.erase(to_remove); mExplicitlyNamed.erase(to_remove); mPendingRootOpen.erase(to_remove); + mHidden.erase(to_remove); mTabs->removeTabPanel(to_remove); to_remove->die(); + updateShowHiddenTabVisibility(); save(); } +void FSInventoryCustomTabs::onHideClicked() +{ + if (isCustomTab(mContextPanel)) + { + hideTab(mContextPanel); + } +} + +void FSInventoryCustomTabs::hideTab(LLInventoryPanel* panel) +{ + if (!mTabs || !isCustomTab(panel) || mHidden.count(panel)) + { + return; + } + const bool was_active = (mTabs->getCurrentPanel() == panel); + mHidden.insert(panel); + mTabs->setTabButtonVisible(panel, false); + if (mContextPanel == panel) + { + mContextPanel = nullptr; + } + if (mLastActivePanel == panel) + { + mLastActivePanel = nullptr; + } + if (was_active) + { + selectNearestVisibleTab(panel); + } + updateShowHiddenTabVisibility(); + save(); +} + +void FSInventoryCustomTabs::showTab(LLInventoryPanel* panel) +{ + if (!mTabs || !isCustomTab(panel) || !mHidden.count(panel)) + { + return; + } + mHidden.erase(panel); + mTabs->setTabButtonVisible(panel, true); + updateShowHiddenTabVisibility(); + mTabs->selectTabPanel(panel); + save(); +} + +void FSInventoryCustomTabs::updateShowHiddenTabVisibility() +{ + if (mTabs && mShowHiddenTabPanel) + { + mTabs->setTabButtonVisible(mShowHiddenTabPanel, !mHidden.empty()); + } +} + +void FSInventoryCustomTabs::selectNearestVisibleTab(LLInventoryPanel* skip_panel) +{ + if (!mTabs) + { + return; + } + + const S32 skip_idx = mTabs->getIndexForPanel(skip_panel); + const S32 count = mTabs->getTabCount(); + const auto selectable = [this](LLPanel* p) -> bool + { + return p && p != static_cast(mAddTabPanel) && p != static_cast(mShowHiddenTabPanel) && mTabs->getTabVisibility(p); + }; + + for (S32 i = skip_idx - 1; i >= 0; --i) + { + if (selectable(mTabs->getPanelByIndex(i))) + { + mTabs->selectTab(i); + return; + } + } + for (S32 i = skip_idx + 1; i < count; ++i) + { + if (selectable(mTabs->getPanelByIndex(i))) + { + mTabs->selectTab(i); + return; + } + } + mTabs->selectFirstTab(); +} + void FSInventoryCustomTabs::clearCustomTabs() { if (!mTabs) @@ -696,7 +926,7 @@ void FSInventoryCustomTabs::clearCustomTabs() mLastActivePanel = nullptr; auto* current = mTabs->getCurrentPanel(); - if (current && (current == mAddTabPanel || mPanels.count(dynamic_cast(current)))) + if (current && (current == mAddTabPanel || current == mShowHiddenTabPanel || mPanels.count(dynamic_cast(current)))) { mTabs->selectFirstTab(); } @@ -705,6 +935,7 @@ void FSInventoryCustomTabs::clearCustomTabs() mPanels.clear(); mExplicitlyNamed.clear(); mPendingRootOpen.clear(); + mHidden.clear(); for (auto* panel : panels_copy) { mTabs->removeTabPanel(panel); @@ -713,6 +944,7 @@ void FSInventoryCustomTabs::clearCustomTabs() mLastActivePanel = nullptr; mContextPanel = nullptr; + updateShowHiddenTabVisibility(); } // --------------------------------------------------------------------------- @@ -747,6 +979,7 @@ void FSInventoryCustomTabs::doLoad() const LLSD tabs = gSavedPerAccountSettings.getLLSD(SETTING_KEY); if (!tabs.isArray()) { + updateShowHiddenTabVisibility(); mLoadedOk = true; return; } @@ -806,9 +1039,16 @@ void FSInventoryCustomTabs::doLoad() new_filter.setSearchType(static_cast(states["search_type"].asInteger())); } } + + if (entry.has(TABHIDDEN_KEY) && entry[TABHIDDEN_KEY].asBoolean()) + { + mHidden.insert(new_panel); + mTabs->setTabButtonVisible(new_panel, false); + } } } + updateShowHiddenTabVisibility(); mLoadedOk = (added == attempted); if (!mLoadedOk) { @@ -887,6 +1127,7 @@ void FSInventoryCustomTabs::save() entry["states"] = states_sd; } + entry[TABHIDDEN_KEY] = static_cast(mHidden.count(inv_panel)); array.append(entry); } @@ -912,7 +1153,7 @@ bool FSInventoryCustomTabs::isUnchangedSetting(const LLSD& array) const } for (LLSD::Integer i = 0; i < array.size(); ++i) { - if (current[i]["name"].asString() != array[i]["name"].asString() || current[i]["filter"].asString() != array[i]["filter"].asString() || !llsd_equals(current[i]["states"], array[i]["states"])) + if (current[i]["name"].asString() != array[i]["name"].asString() || current[i]["filter"].asString() != array[i]["filter"].asString() || current[i][TABHIDDEN_KEY].asBoolean() != array[i][TABHIDDEN_KEY].asBoolean() || !llsd_equals(current[i]["states"], array[i]["states"])) { return false; } @@ -952,6 +1193,11 @@ bool FSInventoryCustomTabs::isAddTab(const LLPanel* panel) const return panel && panel == static_cast(mAddTabPanel); } +bool FSInventoryCustomTabs::isShowHiddenTab(const LLPanel* panel) const +{ + return panel && panel == static_cast(mShowHiddenTabPanel); +} + LLButton* FSInventoryCustomTabs::findTabButton(const LLPanel* panel) const { if (!mTabs || !panel) diff --git a/indra/newview/fsinventorycustomtabs.h b/indra/newview/fsinventorycustomtabs.h index 580c9f7a1c..5822b82ce3 100644 --- a/indra/newview/fsinventorycustomtabs.h +++ b/indra/newview/fsinventorycustomtabs.h @@ -34,6 +34,7 @@ #include class LLButton; +class LLContextMenu; class LLFilterEditor; class LLInventoryPanel; class LLPanel; @@ -61,12 +62,15 @@ public: bool isCustomTab(const LLPanel* panel) const; bool isAddTab(const LLPanel* panel) const; + bool isShowHiddenTab(const LLPanel* panel) const; void onAddTabSelected(); void onAddClicked(); + void onShowHiddenClicked(); void onRenameClicked(); void onCloneClicked(); void onCloseClicked(); + void onHideClicked(); static void install(LLPanelMainInventory* parent, LLFilterEditor* filter_editor); static void notifyFilterStateChanged(LLPanelMainInventory* parent); @@ -89,6 +93,11 @@ private: bool isUnchangedSetting(const LLSD& array) const; void clearCustomTabs(); void installAddTab(); + void installShowHiddenTab(); + void hideTab(LLInventoryPanel* panel); + void showTab(LLInventoryPanel* panel); + void updateShowHiddenTabVisibility(); + void selectNearestVisibleTab(LLInventoryPanel* skip_panel); void onSettingChangedExternally(); void onRenameConfirmed(const LLSD& notification, const LLSD& response); void onCloseConfirmed(const LLSD& notification, const LLSD& response); @@ -110,16 +119,20 @@ private: LLPanelMainInventory* mParent{ nullptr }; LLTabContainer* mTabs{ nullptr }; LLHandle mMenuHandle; + LLHandle mHiddenMenuHandle; std::set mPanels; std::unordered_set mExplicitlyNamed; std::unordered_set mPendingRootOpen; + std::unordered_set mHidden; LLInventoryPanel* mContextPanel{ nullptr }; LLInventoryPanel* mAddTabPanel{ nullptr }; + LLInventoryPanel* mShowHiddenTabPanel{ nullptr }; LLInventoryPanel* mLastActivePanel{ nullptr }; LLNotificationPtr mRenameNotification; LLNotificationPtr mCloseNotification; boost::signals2::connection mSettingConnection; boost::signals2::connection mAddClickConnection; + boost::signals2::connection mShowHiddenClickConnection; bool mSaving{ false }; bool mLoading{ false }; bool mLoadedOk{ false }; diff --git a/indra/newview/skins/default/xui/en/menu_inventory_custom_tab.xml b/indra/newview/skins/default/xui/en/menu_inventory_custom_tab.xml index e5a3928951..2b2fab56a4 100644 --- a/indra/newview/skins/default/xui/en/menu_inventory_custom_tab.xml +++ b/indra/newview/skins/default/xui/en/menu_inventory_custom_tab.xml @@ -17,6 +17,12 @@ + + + Didn't find what you're looking for? Try [secondlife:///app/inventory/filters Show filters]. You haven't marked any items as favorites. Custom + Show hidden tabs To add a place to your landmarks, click the star to the right of the location name. Drag a landmark here to add it to your favorites. You have no listings yet. diff --git a/indra/newview/skins/default/xui/pl/menu_inventory_custom_tab.xml b/indra/newview/skins/default/xui/pl/menu_inventory_custom_tab.xml index d96c322bb6..b3df7d6d3b 100644 --- a/indra/newview/skins/default/xui/pl/menu_inventory_custom_tab.xml +++ b/indra/newview/skins/default/xui/pl/menu_inventory_custom_tab.xml @@ -2,5 +2,6 @@ + diff --git a/indra/newview/skins/default/xui/pl/strings.xml b/indra/newview/skins/default/xui/pl/strings.xml index 472a8251ff..7d49640125 100644 --- a/indra/newview/skins/default/xui/pl/strings.xml +++ b/indra/newview/skins/default/xui/pl/strings.xml @@ -1704,6 +1704,9 @@ https://www.firestormviewer.org/support aby uzyskać pomoc. Własny + + Pokaż ukryte zakładki + Aby dodać miejsce do landmarków, kliknij gwiazdkę po prawej stronie nazwy lokalizacji.