diff options
| author | Profeta <allefalvessilva2011@hotmail.com> | 2026-06-25 16:18:52 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-06-25 16:18:52 -0300 |
| commit | 621a2d8af9b6da91c9f2ccba133fa9aaa6ccdf9b (patch) | |
| tree | 88aa367b376f7037ece073b47ea3561f59be71ab /indra/newview | |
| parent | b7600174a256dce37e274014853974aedc372154 (diff) | |
| parent | 9aaf3e7747dd92ecdbce9878d3ac19a0027845c4 (diff) | |
Merge branch 'develop' into Add-localized-Debug-Settings
Diffstat (limited to 'indra/newview')
| -rw-r--r-- | indra/newview/llagentwearables.cpp | 46 | ||||
| -rw-r--r-- | indra/newview/llagentwearables.h | 1 | ||||
| -rw-r--r-- | indra/newview/llappearancemgr.cpp | 125 | ||||
| -rw-r--r-- | indra/newview/llappearancemgr.h | 13 | ||||
| -rw-r--r-- | indra/newview/llcofwearables.cpp | 66 | ||||
| -rw-r--r-- | indra/newview/llcofwearables.h | 9 | ||||
| -rw-r--r-- | indra/newview/skins/default/xui/en/panel_cof_wearables.xml | 2 | ||||
| -rw-r--r-- | indra/newview/skins/default/xui/en/strings.xml | 1 |
8 files changed, 213 insertions, 50 deletions
diff --git a/indra/newview/llagentwearables.cpp b/indra/newview/llagentwearables.cpp index a075b6f004..edd08c19e5 100644 --- a/indra/newview/llagentwearables.cpp +++ b/indra/newview/llagentwearables.cpp @@ -547,14 +547,23 @@ const S32 LLAgentWearables::getWearableIdxFromItem(const LLViewerInventoryItem* U32 wearable_count = getWearableCount(type); if (0 == wearable_count) return -1; - const LLUUID& asset_id = item->getAssetUUID(); + // Match by linked inventory item id so the correct copy is found when several + // worn wearables of this type share the same asset. + const LLUUID item_id = gInventory.getLinkedItemID(item->getUUID()); + for (U32 i = 0; i < wearable_count; ++i) + { + const LLViewerWearable* wearable = getViewerWearable(type, i); + if (!wearable) continue; + if (wearable->getItemID() == item_id) return i; + } + // Fall back to asset id for items whose inventory id can't be resolved. + const LLUUID& asset_id = item->getAssetUUID(); for (U32 i = 0; i < wearable_count; ++i) { const LLViewerWearable* wearable = getViewerWearable(type, i); if (!wearable) continue; - if (wearable->getAssetID() != asset_id) continue; - return i; + if (wearable->getAssetID() == asset_id) return i; } return -1; @@ -1529,6 +1538,37 @@ bool LLAgentWearables::moveWearable(const LLViewerInventoryItem* item, bool clos return false; } +bool LLAgentWearables::moveWearableToIndex(const LLViewerInventoryItem* item, U32 new_index) +{ + if (!item) return false; + if (!item->isWearableType()) return false; + + LLWearableType::EType type = item->getWearableType(); + U32 wearable_count = getWearableCount(type); + if (wearable_count < 2) return false; + + if (new_index >= wearable_count) new_index = wearable_count - 1; + + S32 cur = getWearableIdxFromItem(item); + if (cur < 0) return false; + if ((U32)cur == new_index) return true; // already in place + + // step the wearable to its new slot one swap at a time + U32 pos = (U32)cur; + while (pos < new_index) + { + if (!swapWearables(type, pos, pos + 1)) return false; + ++pos; + } + while (pos > new_index) + { + if (!swapWearables(type, pos, pos - 1)) return false; + --pos; + } + + return true; +} + // static void LLAgentWearables::createWearable(LLWearableType::EType type, bool wear, const LLUUID& parent_id, std::function<void(const LLUUID&)> created_cb) { diff --git a/indra/newview/llagentwearables.h b/indra/newview/llagentwearables.h index 70da86805c..3c9ffa7443 100644 --- a/indra/newview/llagentwearables.h +++ b/indra/newview/llagentwearables.h @@ -129,6 +129,7 @@ public: static void createWearable(LLWearableType::EType type, bool wear = false, const LLUUID& parent_id = LLUUID::null, std::function<void(const LLUUID&)> created_cb = nullptr); static void editWearable(const LLUUID& item_id); bool moveWearable(const LLViewerInventoryItem* item, bool closer_to_body); + bool moveWearableToIndex(const LLViewerInventoryItem* item, U32 new_index); void requestEditingWearable(const LLUUID& item_id); void editWearableIfRequested(const LLUUID& item_id); diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index 4f549314af..80a3344979 100644 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -4269,77 +4269,110 @@ bool LLAppearanceMgr::moveWearable(LLViewerInventoryItem* item, bool closer_to_b { if (!item || !item->isWearableType()) return false; if (item->getType() != LLAssetType::AT_CLOTHING) return false; - if (!gInventory.isObjectDescendentOf(item->getUUID(), getCOF())) return false; S32 pos = gAgentWearables.getWearableIdxFromItem(item); if (pos < 0) return false; // Not found + if (closer_to_body && pos == 0) return false; // already closest to the body + + return reorderWearable(item, closer_to_body ? (U32)(pos - 1) : (U32)(pos + 1)); +} + +bool LLAppearanceMgr::reorderWearable(LLViewerInventoryItem* item, U32 new_index) +{ + if (!item || !item->isWearableType()) return false; + if (item->getType() != LLAssetType::AT_CLOTHING) return false; + if (!gInventory.isObjectDescendentOf(item->getUUID(), getCOF())) return false; + + LLWearableType::EType type = item->getWearableType(); + U32 count = gAgentWearables.getWearableCount(type); + if (count < 2) return false; // nothing to reorder against + + if (new_index >= count) new_index = count - 1; - U32 count = gAgentWearables.getWearableCount(item->getWearableType()); - if (count < 2) return false; // Nothing to swap with - if (closer_to_body) + S32 cur = gAgentWearables.getWearableIdxFromItem(item); + if (cur < 0) return false; + if ((U32)cur == new_index) return false; // already in place + + // Update the live layer order first; bail before touching inventory if it fails. + if (!gAgentWearables.moveWearableToIndex(item, new_index)) return false; + + persistWearableOrder(type); + return true; +} + +bool LLAppearanceMgr::reorderWearableGroup(LLWearableType::EType type, const uuid_vec_t& ordered_link_ids) +{ + U32 count = gAgentWearables.getWearableCount(type); + if (count < 2) return false; + if (ordered_link_ids.size() != count) return false; // order must cover the whole group + + // Validate everything before mutating, so a bad link can't leave it half-reordered. + for (const LLUUID& link_id : ordered_link_ids) { - if (pos == 0) return false; // already first + LLViewerInventoryItem* link = gInventory.getItem(link_id); + if (!link || link->getWearableType() != type) return false; } - else + + // ordered_link_ids runs furthest-to-closest; body index 0 is closest to the body. + // Place each target at its body index, leaving already-placed lower indices untouched. + for (U32 body_index = 0; body_index < count; ++body_index) { - if (pos == count - 1) return false; // already last + const LLUUID& link_id = ordered_link_ids[count - 1 - body_index]; + LLViewerInventoryItem* link = gInventory.getItem(link_id); + if (!link || link->getWearableType() != type) return false; + if (!gAgentWearables.moveWearableToIndex(link, body_index)) return false; } - U32 old_pos = (U32)pos; - U32 swap_with = closer_to_body ? old_pos - 1 : old_pos + 1; - LLUUID swap_item_id = gAgentWearables.getWearableItemID(item->getWearableType(), swap_with); + persistWearableOrder(type); + return true; +} + +void LLAppearanceMgr::persistWearableOrder(LLWearableType::EType type) +{ + U32 count = gAgentWearables.getWearableCount(type); - // Find link item from item id. + // Rewrite the sort-index descriptions for the whole type group in one pass so + // the order survives relog, trusting gAgentWearables over existing descriptions. LLInventoryModel::cat_array_t cats; LLInventoryModel::item_array_t items; - LLFindWearablesOfType filter_wearables_of_type(item->getWearableType()); + LLFindWearablesOfType filter_wearables_of_type(type); gInventory.collectDescendentsIf(getCOF(), cats, items, true, filter_wearables_of_type); - if (items.empty()) return false; - LLViewerInventoryItem* swap_item = nullptr; - for (auto iter : items) + for (U32 i = 0; i < count; ++i) { - if (iter->getLinkedUUID() == swap_item_id) + LLUUID linked_id = gAgentWearables.getWearableItemID(type, i); + if (linked_id.isNull()) continue; + + LLViewerInventoryItem* link = nullptr; + for (auto iter : items) { - swap_item = iter.get(); - break; + if (iter->getLinkedUUID() == linked_id) + { + link = iter.get(); + break; + } } - } - if (!swap_item) - { - return false; - } + if (!link) continue; - // Description is supposed to hold sort index, but user could have changed - // order rapidly and there might be a state mismatch between description - // and gAgentWearables, trust gAgentWearables over description. - // Generate new description. - std::string new_desc = build_order_string(item->getWearableType(), old_pos); - swap_item->setDescription(new_desc); - new_desc = build_order_string(item->getWearableType(), swap_with); - item->setDescription(new_desc); + std::string new_desc = build_order_string(type, i); + if (new_desc == link->getActualDescription()) continue; - item->setComplete(true); - item->updateServer(false); - gInventory.updateItem(item); - - swap_item->setComplete(true); - swap_item->updateServer(false); - gInventory.updateItem(swap_item); + // Keep the local cache consistent immediately (so the COF list does not + // flicker back on a refresh), and persist durably via AISv3, matching + // updateClothingOrderingInfo() rather than the legacy UDP updateServer(). + link->setDescription(new_desc); + LLSD updates; + updates["desc"] = new_desc; + update_inventory_item(link->getUUID(), updates, NULL); + } - //to cause appearance of the agent to be updated - bool result = false; - if ((result = gAgentWearables.moveWearable(item, closer_to_body))) + if (isAgentAvatarValid()) { - gAgentAvatarp->wearableUpdated(item->getWearableType()); + gAgentAvatarp->wearableUpdated(type); } setOutfitDirty(true); - - //*TODO do we need to notify observers here in such a way? gInventory.notifyObservers(); - - return result; } //static diff --git a/indra/newview/llappearancemgr.h b/indra/newview/llappearancemgr.h index 9d90be0004..77146ba0be 100644 --- a/indra/newview/llappearancemgr.h +++ b/indra/newview/llappearancemgr.h @@ -210,6 +210,15 @@ public: bool moveWearable(LLViewerInventoryItem* item, bool closer_to_body); + // Move a clothing item to an absolute layer index within its wearable type + // (0 == closest to the body). Persists the new order to the COF link descriptions. + bool reorderWearable(LLViewerInventoryItem* item, U32 new_index); + + // Apply a complete layer order for one wearable type, persisting it to the + // COF link descriptions. ordered_link_ids lists the type's COF link items + // furthest-to-closest. + bool reorderWearableGroup(LLWearableType::EType type, const uuid_vec_t& ordered_link_ids); + static void sortItemsByActualDescription(LLInventoryModel::item_array_t& items); //Divvy items into arrays by wearable type @@ -250,6 +259,10 @@ private: private: + // Rewrite COF sort-index descriptions for one wearable type to match the + // current in-memory layer order, then trigger a single appearance update. + void persistWearableOrder(LLWearableType::EType type); + void filterWearableItems(LLInventoryModel::item_array_t& items, S32 max_per_type, S32 max_total, bool skip_bodyparts = false); void getDescendentsOfAssetType(const LLUUID& category, diff --git a/indra/newview/llcofwearables.cpp b/indra/newview/llcofwearables.cpp index 6bd15d4b61..f5d144dcc6 100644 --- a/indra/newview/llcofwearables.cpp +++ b/indra/newview/llcofwearables.cpp @@ -292,7 +292,8 @@ LLCOFWearables::LLCOFWearables() : LLPanel(), mBodyPartsTab(NULL), mLastSelectedTab(NULL), mAccordionCtrl(NULL), - mCOFVersion(-1) + mCOFVersion(-1), + mRefreshPending(false) { mClothingMenu = new CofClothingContextMenu(this); mAttachmentMenu = new CofAttachmentContextMenu(this); @@ -326,6 +327,10 @@ bool LLCOFWearables::postBuild() mClothing->setCommitOnSelectionChange(true); mBodyParts->setCommitOnSelectionChange(true); + mClothing->setReorderValidateCallback(boost::bind(&LLCOFWearables::canReorderClothing, this, _1, _2)); + mClothing->setReorderCallback(boost::bind(&LLCOFWearables::onClothingReordered, this, _1, _2)); + mClothing->setReorderEndedCallback(boost::bind(&LLCOFWearables::onReorderEnded, this)); + //clothing is sorted according to its position relatively to the body mAttachments->setComparator(&WEARABLE_NAME_COMPARATOR); mBodyParts->setComparator(&WEARABLE_NAME_COMPARATOR); @@ -377,6 +382,50 @@ void LLCOFWearables::onSelectionChange(LLFlatListView* selected_list) onCommit(); } +bool LLCOFWearables::canReorderClothing(const LLSD& dragged_value, const LLSD& neighbour_value) +{ + LLViewerInventoryItem* dragged = gInventory.getItem(dragged_value.asUUID()); + LLViewerInventoryItem* neighbour = gInventory.getItem(neighbour_value.asUUID()); + if (!dragged || !neighbour) return false; // reject placeholder/dummy rows + if (!dragged->isWearableType() || !neighbour->isWearableType()) return false; + + return dragged->getWearableType() == neighbour->getWearableType(); +} + +void LLCOFWearables::onClothingReordered(const LLSD& dragged_value, S32 /*new_index*/) +{ + LLViewerInventoryItem* item = gInventory.getItem(dragged_value.asUUID()); + if (!item || !item->isWearableType()) return; + + LLWearableType::EType type = item->getWearableType(); + + // Persist the affected type group's full order from the list's current order + // (furthest-to-closest), which covers both single- and multi-row drags. + uuid_vec_t ordered_link_ids; + std::vector<LLSD> values; + mClothing->getValues(values); + for (const LLSD& value : values) + { + LLViewerInventoryItem* other = gInventory.getItem(value.asUUID()); + if (!other || !other->isWearableType() || other->getWearableType() != type) continue; + + ordered_link_ids.push_back(value.asUUID()); + } + + if (ordered_link_ids.size() < 2) return; + + LLAppearanceMgr::getInstance()->reorderWearableGroup(type, ordered_link_ids); +} + +void LLCOFWearables::onReorderEnded() +{ + // Run a refresh that arrived (and was skipped) while the drag was active. + if (mRefreshPending) + { + refresh(); + } +} + void LLCOFWearables::onAccordionTabStateChanged(LLUICtrl* ctrl, const LLSD& expanded) { bool had_selected_items = mClothing->numSelected() || mAttachments->numSelected() || mBodyParts->numSelected(); @@ -401,6 +450,15 @@ void LLCOFWearables::onAccordionTabStateChanged(LLUICtrl* ctrl, const LLSD& expa void LLCOFWearables::refresh() { + // Don't rebuild mid-drag, clearing the list would cancel the active reorder. + // Remember it so onReorderEnded() can re-run the skipped refresh on release. + if (mClothing && mClothing->isReorderActive()) + { + mRefreshPending = true; + return; + } + mRefreshPending = false; + const LLUUID cof_id = LLAppearanceMgr::instance().getCOF(); if (cof_id.isNull()) { @@ -560,6 +618,12 @@ LLPanelClothingListItem* LLCOFWearables::buildClothingListItem(LLViewerInventory item_panel->setShowMoveUpButton(!first); item_panel->setShowMoveDownButton(!last); + // hint that rows in a multi-layer group can be dragged to reorder + if (!(first && last)) + { + item_panel->setToolTip(LLTrans::getString("ReorderClothingTooltip")); + } + //setting callbacks //*TODO move that item panel's inner structure disclosing stuff into the panels item_panel->childSetAction("btn_delete", boost::bind(mCOFCallbacks.mDeleteWearable)); diff --git a/indra/newview/llcofwearables.h b/indra/newview/llcofwearables.h index b349f35921..7d4d8917b4 100644 --- a/indra/newview/llcofwearables.h +++ b/indra/newview/llcofwearables.h @@ -102,6 +102,12 @@ protected: void onSelectionChange(LLFlatListView* selected_list); void onAccordionTabStateChanged(LLUICtrl* ctrl, const LLSD& expanded); + // Clothing drag-to-reorder: only allow moves within the same wearable type, + // and translate a drop into a layer-order change. + bool canReorderClothing(const LLSD& dragged_value, const LLSD& neighbour_value); + void onClothingReordered(const LLSD& dragged_value, S32 new_index); + void onReorderEnded(); + LLPanelClothingListItem* buildClothingListItem(LLViewerInventoryItem* item, bool first, bool last); LLPanelBodyPartsListItem* buildBodypartListItem(LLViewerInventoryItem* item); LLPanelDeletableWearableListItem* buildAttachemntListItem(LLViewerInventoryItem* item); @@ -132,6 +138,9 @@ protected: /* COF category version since last refresh */ S32 mCOFVersion; + + /* a refresh arrived mid-drag and was skipped; re-run it when the drag ends */ + bool mRefreshPending; }; diff --git a/indra/newview/skins/default/xui/en/panel_cof_wearables.xml b/indra/newview/skins/default/xui/en/panel_cof_wearables.xml index 9544042e94..37867b4a0e 100644 --- a/indra/newview/skins/default/xui/en/panel_cof_wearables.xml +++ b/indra/newview/skins/default/xui/en/panel_cof_wearables.xml @@ -26,10 +26,12 @@ title="Clothing"> <flat_list_view allow_select="true" + allow_reorder="true" follows="all" height="10" item_pad="3" keep_selection_visible_on_reshape="true" + keep_scroll_pos="true" layout="topleft" left="0" multi_select="true" diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index a8fa4b735a..e44a18ae79 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -225,6 +225,7 @@ If you feel this is an error, please contact support@secondlife.com</string> <string name="Toycamera">Toy Camera</string> <!-- Tooltip --> + <string name="ReorderClothingTooltip">Drag to reorder layers, or use the arrows</string> <!-- Hint shown on clothing rows that can be dragged to reorder --> <string name="TooltipPerson">Person</string><!-- Object under mouse pointer is an avatar --> <string name="TooltipNoName">(no name)</string> <!-- No name on an object --> <string name="TooltipOwner">Owner:</string> <!-- Owner name follows --> |
