From db325e04af49473624cfa55708c63623fcc3cbd6 Mon Sep 17 00:00:00 2001 From: gwigz Date: Fri, 26 Jun 2026 18:59:06 +0100 Subject: [PATCH] Port clothing layer drag-and-drop refinements Brings the COF clothing layer reorder feature up to the final upstream state (secondlife/viewer#5918 + secondlife/viewer#5959), on top of the two commits already cherry-picked here: - LLScrollContainer: replace the bespoke preserveScrollbarMetrics helper with a keep_scroll_pos param, so the clothing list no longer jumps its scroll position on rebuild - LLFlatListView: arm reorder drag in inter-row gaps (handleMouseDown + getReorderPairAt), cancel an active drag on visibility change (alt-tab), replace the mProcessingRightClick hack with EMouseClickType, add a reorder-ended callback fired on cancelled drags, report the moved row's visible index, plus the secondlife/viewer#5959 nullptr/constexpr/formatting cleanup - LLCOFWearables: defer a refresh that arrives mid-drag and re-run it when the drag ends, so a COF change can't cancel an active reorder - panel_cof_wearables.xml: keep_scroll_pos="true" on the clothing list Co-Authored-By: Claude Opus 4.8 (1M context) --- indra/llui/llflatlistview.cpp | 162 +++++++++++++----- indra/llui/llflatlistview.h | 17 +- indra/llui/llscrollcontainer.cpp | 62 ++++--- indra/llui/llscrollcontainer.h | 5 +- indra/newview/llappearancemgr.h | 7 +- indra/newview/llcofwearables.cpp | 22 ++- indra/newview/llcofwearables.h | 4 + .../default/xui/en/panel_cof_wearables.xml | 1 + 8 files changed, 211 insertions(+), 69 deletions(-) diff --git a/indra/llui/llflatlistview.cpp b/indra/llui/llflatlistview.cpp index a514417781..33a72c1d2e 100644 --- a/indra/llui/llflatlistview.cpp +++ b/indra/llui/llflatlistview.cpp @@ -110,7 +110,7 @@ bool LLFlatListView::addItem(LLPanel * item, const LLSD& value /*= LLUUID::null* } //_4 is for MASK - item->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4)); + item->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4, CLICK_LEFT)); item->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemRightMouseClick, this, new_pair, _4)); // Children don't accept the focus @@ -168,7 +168,7 @@ bool LLFlatListView::addItemPairs(pairs_list_t panel_list, bool rearrange /*= tr mItemsPanel->addChild(panel); //_4 is for MASK - panel->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4)); + panel->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4, CLICK_LEFT)); panel->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemRightMouseClick, this, new_pair, _4)); // Children don't accept the focus panel->setTabStop(false); @@ -195,7 +195,7 @@ bool LLFlatListView::addItemPairs(pairs_list_t panel_list, bool rearrange /*= tr mItemsPanel->addChild(panel); //_4 is for MASK - panel->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, item_pair, _4)); + panel->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, item_pair, _4, CLICK_LEFT)); panel->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemRightMouseClick, this, item_pair, _4)); // Children don't accept the focus panel->setTabStop(false); @@ -247,7 +247,7 @@ bool LLFlatListView::insertItemAfter(LLPanel* after_item, LLPanel* item_to_add, } //_4 is for MASK - item_to_add->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4)); + item_to_add->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4, CLICK_LEFT)); item_to_add->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemRightMouseClick, this, new_pair, _4)); rearrangeItems(); @@ -504,9 +504,8 @@ LLFlatListView::LLFlatListView(const LLFlatListView::Params& p) , mFocusOnItemClicked(true) , mAllowReorder(p.allow_reorder) , mIsReordering(false) - , mProcessingRightClick(false) - , mReorderDragPair(NULL) - , mDeferredSelectPair(NULL) + , mReorderDragPair(nullptr) + , mDeferredSelectPair(nullptr) , mReorderMouseDownX(0) , mReorderMouseDownY(0) , mReorderInsertIndex(-1) @@ -672,7 +671,7 @@ void LLFlatListView::rearrangeItems() mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1)); } -void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask) +void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask, EMouseClickType clicktype) { if (!item_pair) return; @@ -687,7 +686,7 @@ void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask) setFocus(true); } - if (!(mask & (MASK_SHIFT | MASK_CONTROL))) + if (clicktype == CLICK_LEFT && !(mask & (MASK_SHIFT | MASK_CONTROL))) { armReorderDrag(item_pair); } @@ -801,16 +800,14 @@ void LLFlatListView::onItemRightMouseClick(item_pair_t* item_pair, MASK mask) return; // else got same behavior as at onItemMouseClick, but a right click must never start a drag - mProcessingRightClick = true; - onItemMouseClick(item_pair, mask); - mProcessingRightClick = false; + onItemMouseClick(item_pair, mask, CLICK_RIGHT); } -static const S32 REORDER_DRAG_THRESHOLD = 5; +static constexpr S32 REORDER_DRAG_THRESHOLD = 5; void LLFlatListView::armReorderDrag(item_pair_t* item_pair) { - if (!mAllowReorder || mProcessingRightClick) + if (!mAllowReorder) { return; } @@ -832,7 +829,7 @@ void LLFlatListView::armReorderDrag(item_pair_t* item_pair) mReorderDragPair = item_pair; mReorderDragGroup.clear(); - mDeferredSelectPair = NULL; + mDeferredSelectPair = nullptr; mIsReordering = false; mReorderInsertIndex = -1; @@ -842,7 +839,8 @@ void LLFlatListView::armReorderDrag(item_pair_t* item_pair) void LLFlatListView::updateReorderDrag(S32 x, S32 y) { - if (!mReorderDragPair) return; + if (!mReorderDragPair) + return; if (!mIsReordering) { @@ -866,7 +864,8 @@ void LLFlatListView::buildReorderGroup() for (item_pair_t* pair : mItemPairs) { - if (!pair->first->getVisible()) continue; + if (!pair->first->getVisible()) + continue; if (pair == mReorderDragPair) { @@ -891,7 +890,8 @@ void LLFlatListView::getReorderRemaining(pairs_list_t& remaining) const remaining.clear(); for (item_pair_t* pair : mItemPairs) { - if (!pair->first->getVisible() || isInReorderGroup(pair)) continue; + if (!pair->first->getVisible() || isInReorderGroup(pair)) + continue; remaining.push_back(pair); } } @@ -903,12 +903,16 @@ void LLFlatListView::finishReorderDrag() pairs_list_t remaining; getReorderRemaining(remaining); - // Resolve the drop boundary to the remaining row it lands before (NULL = append). - item_pair_t* anchor = NULL; + // Resolve the drop boundary to the remaining row it lands before (nullptr = append). + item_pair_t* anchor = nullptr; S32 cur = 0; for (item_pair_t* pair : remaining) { - if (cur == mReorderInsertIndex) { anchor = pair; break; } + if (cur == mReorderInsertIndex) + { + anchor = pair; + break; + } ++cur; } @@ -919,7 +923,7 @@ void LLFlatListView::finishReorderDrag() mItemPairs.remove(pair); } - pairs_iterator_t it = (anchor != NULL) + pairs_iterator_t it = (anchor != nullptr) ? std::find(mItemPairs.begin(), mItemPairs.end(), anchor) : mItemPairs.end(); for (item_pair_t* pair : mReorderDragGroup) @@ -931,7 +935,17 @@ void LLFlatListView::finishReorderDrag() if (mReorderCallback) { - mReorderCallback(moved_value, mReorderInsertIndex); + S32 visible_index = 0; + for (item_pair_t* pair : mItemPairs) + { + if (!pair->first->getVisible()) + continue; + if (pair == mReorderDragPair) + break; + ++visible_index; + } + + mReorderCallback(moved_value, visible_index); } } @@ -942,7 +956,7 @@ void LLFlatListView::cancelReorderDrag() { if (hasMouseCapture()) { - gFocusMgr.setMouseCapture(NULL); + gFocusMgr.setMouseCapture(nullptr); } clearReorderDragState(); @@ -954,11 +968,19 @@ void LLFlatListView::clearReorderDragState() { getWindow()->setCursor(UI_CURSOR_ARROW); } - mReorderDragPair = NULL; + + bool was_armed = (mReorderDragPair != nullptr); + + mReorderDragPair = nullptr; mReorderDragGroup.clear(); - mDeferredSelectPair = NULL; + mDeferredSelectPair = nullptr; mIsReordering = false; mReorderInsertIndex = -1; + + if (was_armed && mReorderEndedCallback) + { + mReorderEndedCallback(); + } } void LLFlatListView::onMouseCaptureLost() @@ -966,6 +988,16 @@ void LLFlatListView::onMouseCaptureLost() clearReorderDragState(); } +void LLFlatListView::onVisibilityChange(bool new_visibility) +{ + if (!new_visibility && mReorderDragPair) + { + cancelReorderDrag(); + } + + LLScrollContainer::onVisibilityChange(new_visibility); +} + S32 LLFlatListView::getInsertIndexAt(S32 x, S32 y) const { S32 panel_x, panel_y; @@ -976,7 +1008,8 @@ S32 LLFlatListView::getInsertIndexAt(S32 x, S32 y) const S32 index = 0; for (item_pair_t* pair : mItemPairs) { - if (!pair->first->getVisible() || isInReorderGroup(pair)) continue; + if (!pair->first->getVisible() || isInReorderGroup(pair)) + continue; if (pair->first->getRect().getCenterY() > panel_y) { @@ -986,9 +1019,31 @@ S32 LLFlatListView::getInsertIndexAt(S32 x, S32 y) const return index; } +LLFlatListView::item_pair_t* LLFlatListView::getReorderPairAt(S32 x, S32 y) const +{ + S32 panel_x, panel_y; + localPointToOtherView(x, y, &panel_x, &panel_y, mItemsPanel); + + for (item_pair_t* pair : mItemPairs) + { + if (!pair->first->getVisible()) + continue; + + // Claim the padding above each row so gap presses still resolve to a row. + LLRect rc = pair->first->getRect(); + rc.mTop += mItemPad; + if (rc.pointInRect(panel_x, panel_y)) + { + return pair; + } + } + return nullptr; +} + S32 LLFlatListView::constrainInsertIndex(S32 dest_index) const { - if (!mReorderValidateCallback) return dest_index; + if (!mReorderValidateCallback) + return dest_index; // Clamp the boundary to the contiguous run of remaining rows that share the // grabbed row's group, so a drag can't leave its group. @@ -1003,16 +1058,20 @@ S32 LLFlatListView::constrainInsertIndex(S32 dest_index) const { if (mReorderValidateCallback(dragged, pair->second)) { - if (first_valid < 0) first_valid = i; + if (first_valid < 0) + first_valid = i; last_valid = i; } ++i; } - if (first_valid < 0) return -1; // no valid neighbour (whole group is being dragged) + if (first_valid < 0) + return -1; // no valid neighbour (whole group is being dragged) - if (dest_index < first_valid) return first_valid; - if (dest_index > last_valid + 1) return last_valid + 1; + if (dest_index < first_valid) + return first_valid; + if (dest_index > last_valid + 1) + return last_valid + 1; return dest_index; } @@ -1020,7 +1079,8 @@ void LLFlatListView::drawReorderIndicator() { pairs_list_t remaining; getReorderRemaining(remaining); - if (remaining.empty()) return; + if (remaining.empty()) + return; const LLRect& panel_rc = mItemsPanel->getRect(); const LLColor4& color = mDragIndicatorColor.get(); @@ -1039,21 +1099,27 @@ void LLFlatListView::drawReorderIndicator() bool at_end = mReorderInsertIndex >= count; S32 anchor_idx = at_end ? count - 1 : mReorderInsertIndex; - item_pair_t* anchor = NULL; + item_pair_t* anchor = nullptr; S32 cur = 0; for (item_pair_t* pair : remaining) { - if (cur == anchor_idx) { anchor = pair; break; } + if (cur == anchor_idx) + { + anchor = pair; + break; + } ++cur; } - if (!anchor) return; + if (!anchor) + return; const LLRect& item_rc = anchor->first->getRect(); S32 left = panel_rc.mLeft + item_rc.mLeft; S32 right = panel_rc.mLeft + item_rc.mRight; S32 line_y = panel_rc.mBottom + (at_end ? item_rc.mBottom : item_rc.mTop); - if (line_y < 0 || line_y > getRect().getHeight()) return; + if (line_y < 0 || line_y > getRect().getHeight()) + return; gl_rect_2d(left, line_y, right, line_y - 1, color, true); } @@ -1072,13 +1138,31 @@ bool LLFlatListView::handleHover(S32 x, S32 y, MASK mask) return LLScrollContainer::handleHover(x, y, mask); } +bool LLFlatListView::handleMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = LLScrollContainer::handleMouseDown(x, y, mask); + + // A press in the padding between rows misses every item, so the per-item + // mouse-down never arms a reorder. Map it to the nearest row and arm there; + // armReorderDrag's capture guard ignores presses a child control already took. + if (mAllowReorder && !mReorderDragPair && !(mask & (MASK_CONTROL | MASK_SHIFT))) + { + if (item_pair_t* pair = getReorderPairAt(x, y)) + { + onItemMouseClick(pair, mask, CLICK_LEFT); + } + } + + return handled || (mReorderDragPair != nullptr); +} + bool LLFlatListView::handleMouseUp(S32 x, S32 y, MASK mask) { if (mReorderDragPair && hasMouseCapture()) { bool was_reordering = mIsReordering; item_pair_t* deferred = mDeferredSelectPair; - mDeferredSelectPair = NULL; + mDeferredSelectPair = nullptr; finishReorderDrag(); @@ -1475,7 +1559,7 @@ bool LLFlatListView::removeItemPair(item_pair_t* item_pair, bool rearrange) } if (item_pair == mDeferredSelectPair) { - mDeferredSelectPair = NULL; + mDeferredSelectPair = nullptr; } bool deleted = false; diff --git a/indra/llui/llflatlistview.h b/indra/llui/llflatlistview.h index 94f491a788..75356faf34 100644 --- a/indra/llui/llflatlistview.h +++ b/indra/llui/llflatlistview.h @@ -288,6 +288,12 @@ class LLFlatListView : public LLScrollContainer, public LLEditMenuHandler void setReorderCallback(reorder_signal_t cb) { mReorderCallback = cb; } void setReorderValidateCallback(reorder_validate_signal_t cb) { mReorderValidateCallback = cb; } + /** Fired when a reorder grab is released, whether or not it moved anything */ + void setReorderEndedCallback(boost::function cb) { mReorderEndedCallback = cb; } + + /** True while an item is grabbed for a drag-to-reorder (before or during the drag) */ + bool isReorderActive() const { return mReorderDragPair != nullptr; } + /** Sets flag whether onCommit should be fired if selection was changed */ // FIXME: this should really be a separate signal, since "Commit" implies explicit user action, and selection changes can happen more indirectly. void setCommitOnSelectionChange(bool b) { mCommitOnSelectionChange = b; } @@ -359,7 +365,7 @@ class LLFlatListView : public LLScrollContainer, public LLEditMenuHandler LLFlatListView(const LLFlatListView::Params& p); /** Manage selection on mouse events */ - void onItemMouseClick(item_pair_t* item_pair, MASK mask); + void onItemMouseClick(item_pair_t* item_pair, MASK mask, EMouseClickType clicktype); void onItemRightMouseClick(item_pair_t* item_pair, MASK mask); @@ -399,10 +405,14 @@ class LLFlatListView : public LLScrollContainer, public LLEditMenuHandler virtual bool handleHover(S32 x, S32 y, MASK mask) override; + virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; + virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override; virtual void onMouseCaptureLost() override; + virtual void onVisibilityChange(bool new_visibility) override; + virtual bool postBuild() override; virtual void onFocusReceived() override; @@ -435,6 +445,9 @@ class LLFlatListView : public LLScrollContainer, public LLEditMenuHandler void getReorderRemaining(pairs_list_t& remaining) const; // Number of leading non-dragged items whose centre sits above (x, y). S32 getInsertIndexAt(S32 x, S32 y) const; + // Row under (x, y), counting the padding above each row, so presses in the + // inter-row gaps still map to a row instead of falling through. + item_pair_t* getReorderPairAt(S32 x, S32 y) const; // Clamps an insertion boundary to the validator's contiguous group. S32 constrainInsertIndex(S32 dest_index) const; void drawReorderIndicator(); @@ -485,7 +498,6 @@ class LLFlatListView : public LLScrollContainer, public LLEditMenuHandler /** Drag-to-reorder state */ bool mAllowReorder; bool mIsReordering; // a drag is currently in progress - bool mProcessingRightClick; // suppress drag arming during right-click handling item_pair_t* mReorderDragPair; // the grabbed pair (drag anchor) pairs_list_t mReorderDragGroup; // all pairs being dragged, in visual order item_pair_t* mDeferredSelectPair; // collapse selection to this on mouse-up if no drag @@ -495,6 +507,7 @@ class LLFlatListView : public LLScrollContainer, public LLEditMenuHandler LLUIColor mDragIndicatorColor; reorder_signal_t mReorderCallback; reorder_validate_signal_t mReorderValidateCallback; + boost::function mReorderEndedCallback; /** All pairs of the list */ pairs_list_t mItemPairs; diff --git a/indra/llui/llscrollcontainer.cpp b/indra/llui/llscrollcontainer.cpp index 02b2fc144e..66e635211d 100644 --- a/indra/llui/llscrollcontainer.cpp +++ b/indra/llui/llscrollcontainer.cpp @@ -75,6 +75,7 @@ LLScrollContainer::Params::Params() max_auto_scroll_rate("max_auto_scroll_rate", 1000), max_auto_scroll_zone("max_auto_scroll_zone", 16), reserve_scroll_corner("reserve_scroll_corner", false), + keep_scroll_pos("keep_scroll_pos", false), size("size", -1) {} @@ -93,6 +94,7 @@ LLScrollContainer::LLScrollContainer(const LLScrollContainer::Params& p) mMaxAutoScrollRate(p.max_auto_scroll_rate), mMaxAutoScrollZone(p.max_auto_scroll_zone), mScrolledView(NULL), + mKeepScrollPos(p.keep_scroll_pos), mSize(p.size) { mStoredDocPos[VERTICAL] = 0; @@ -200,8 +202,11 @@ void LLScrollContainer::reshape(S32 width, S32 height, bool show_h_scrollbar = false; calcVisibleSize( &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); - preserveScrollbarMetrics(VERTICAL, show_v_scrollbar, scrolled_rect.getHeight(), visible_height); - preserveScrollbarMetrics(HORIZONTAL, show_h_scrollbar, scrolled_rect.getWidth(), visible_width); + mScrollbar[VERTICAL]->setDocSize( scrolled_rect.getHeight() ); + mScrollbar[VERTICAL]->setPageSize( visible_height ); + + mScrollbar[HORIZONTAL]->setDocSize( scrolled_rect.getWidth() ); + mScrollbar[HORIZONTAL]->setPageSize( visible_width ); updateScroll(); } } @@ -586,24 +591,6 @@ bool LLScrollContainer::addChild(LLView* view, S32 tab_group) return ret_val; } -void LLScrollContainer::preserveScrollbarMetrics(EOrientation axis, bool show, S32 doc_size, S32 page_size) -{ - // Snapshot the position before the resize, but only while the scrollbar is - // visible: during a transient full-content pass it hides and its position is - // forced to 0, which would otherwise clobber the remembered position. - if (show && mScrollbar[axis]->getVisible()) - { - mStoredDocPos[axis] = mScrollbar[axis]->getDocPos(); - } - mScrollbar[axis]->setDocSize(doc_size); - mScrollbar[axis]->setPageSize(page_size); - if (show) - { - mScrollbar[axis]->setDocPos(mStoredDocPos[axis]); - mStoredDocPos[axis] = mScrollbar[axis]->getDocPos(); - } -} - void LLScrollContainer::updateScroll() { if (!getVisible() || !mScrolledView) @@ -623,8 +610,21 @@ void LLScrollContainer::updateScroll() calcVisibleSize( &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); S32 border_width = getBorderWidth(); - preserveScrollbarMetrics(VERTICAL, show_v_scrollbar, doc_height, visible_height); - preserveScrollbarMetrics(HORIZONTAL, show_h_scrollbar, doc_width, visible_width); + + // Remember the position only while the scrollbar is genuinely showing scrollable + // content, so a transient empty pass (e.g. a list clearing before it repopulates) + // cannot overwrite it with 0. + if (mKeepScrollPos) + { + if (show_v_scrollbar && mScrollbar[VERTICAL]->getVisible()) + { + mStoredDocPos[VERTICAL] = mScrollbar[VERTICAL]->getDocPos(); + } + if (show_h_scrollbar && mScrollbar[HORIZONTAL]->getVisible()) + { + mStoredDocPos[HORIZONTAL] = mScrollbar[HORIZONTAL]->getDocPos(); + } + } if( show_v_scrollbar ) { @@ -688,6 +688,24 @@ void LLScrollContainer::updateScroll() mScrollbar[HORIZONTAL]->setVisible( false ); mScrollbar[HORIZONTAL]->setDocPos( 0 ); } + + mScrollbar[HORIZONTAL]->setDocSize( doc_width ); + mScrollbar[HORIZONTAL]->setPageSize( visible_width ); + + mScrollbar[VERTICAL]->setDocSize( doc_height ); + mScrollbar[VERTICAL]->setPageSize( visible_height ); + + if (mKeepScrollPos) + { + if (show_v_scrollbar) + { + mScrollbar[VERTICAL]->setDocPos(mStoredDocPos[VERTICAL]); + } + if (show_h_scrollbar) + { + mScrollbar[HORIZONTAL]->setDocPos(mStoredDocPos[HORIZONTAL]); + } + } } // end updateScroll void LLScrollContainer::setBorderVisible(bool b) diff --git a/indra/llui/llscrollcontainer.h b/indra/llui/llscrollcontainer.h index c529678eb1..f4a59ae2c3 100644 --- a/indra/llui/llscrollcontainer.h +++ b/indra/llui/llscrollcontainer.h @@ -64,7 +64,8 @@ class LLScrollContainer : public LLUICtrl reserve_scroll_corner, border_visible, hide_scrollbar, - ignore_arrow_keys; + ignore_arrow_keys, + keep_scroll_pos; Optional min_auto_scroll_rate, max_auto_scroll_rate; Optional max_auto_scroll_zone; @@ -136,10 +137,10 @@ class LLScrollContainer : public LLUICtrl void updateScroll(); bool autoScroll(S32 x, S32 y, bool do_scroll); void calcVisibleSize( S32 *visible_width, S32 *visible_height, bool* show_h_scrollbar, bool* show_v_scrollbar ) const; - void preserveScrollbarMetrics(EOrientation axis, bool show, S32 doc_size, S32 page_size); LLScrollbar* mScrollbar[ORIENTATION_COUNT]; S32 mStoredDocPos[ORIENTATION_COUNT]; + bool mKeepScrollPos; S32 mSize; bool mIsOpaque; LLUIColor mBackgroundColor; diff --git a/indra/newview/llappearancemgr.h b/indra/newview/llappearancemgr.h index 31b10338e4..684e55f30e 100644 --- a/indra/newview/llappearancemgr.h +++ b/indra/newview/llappearancemgr.h @@ -226,11 +226,12 @@ class LLAppearanceMgr: public LLSingleton 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 in a single batch. + // (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 in a single batch. - // ordered_link_ids lists the type's COF link items furthest-to-closest. + // 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); diff --git a/indra/newview/llcofwearables.cpp b/indra/newview/llcofwearables.cpp index dac7bdc407..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); @@ -328,6 +329,7 @@ bool LLCOFWearables::postBuild() 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); @@ -415,6 +417,15 @@ void LLCOFWearables::onClothingReordered(const LLSD& dragged_value, S32 /*new_in 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(); @@ -439,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()) { diff --git a/indra/newview/llcofwearables.h b/indra/newview/llcofwearables.h index 4013cbdb2d..7d4d8917b4 100644 --- a/indra/newview/llcofwearables.h +++ b/indra/newview/llcofwearables.h @@ -106,6 +106,7 @@ class LLCOFWearables : public LLPanel // 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); @@ -137,6 +138,9 @@ class LLCOFWearables : public LLPanel /* 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 ef2ffce30c..37867b4a0e 100644 --- a/indra/newview/skins/default/xui/en/panel_cof_wearables.xml +++ b/indra/newview/skins/default/xui/en/panel_cof_wearables.xml @@ -31,6 +31,7 @@ height="10" item_pad="3" keep_selection_visible_on_reshape="true" + keep_scroll_pos="true" layout="topleft" left="0" multi_select="true"