diff options
| author | gwigz <gwigz@users.noreply.github.com> | 2026-06-25 20:08:15 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-06-25 22:08:15 +0300 |
| commit | 9aaf3e7747dd92ecdbce9878d3ac19a0027845c4 (patch) | |
| tree | c69d5b84444b323bfa5105e6a1c336a631632e95 /indra/llui | |
| parent | 7d8e9d97c9f485ca413c98f4a543efe2bbd15bd9 (diff) | |
@5929 Add drag-n-drop reordering for clothing layers (#5918)
- Drag-and-drop reordering for clothing layers with visual insertion indicator
- Includes new `keep_scroll_pos` param to preserve and restore scroll positions during layout and resize
Diffstat (limited to 'indra/llui')
| -rw-r--r-- | indra/llui/llflatlistview.cpp | 450 | ||||
| -rw-r--r-- | indra/llui/llflatlistview.h | 78 | ||||
| -rw-r--r-- | indra/llui/llscrollcontainer.cpp | 33 | ||||
| -rw-r--r-- | indra/llui/llscrollcontainer.h | 5 |
4 files changed, 556 insertions, 10 deletions
diff --git a/indra/llui/llflatlistview.cpp b/indra/llui/llflatlistview.cpp index 34eb1ea3fc..64e47cea6e 100644 --- a/indra/llui/llflatlistview.cpp +++ b/indra/llui/llflatlistview.cpp @@ -31,6 +31,14 @@ #include "llflatlistview.h" +#include "llfocusmgr.h" +#include "llrender2dutils.h" +#include "llui.h" +#include "lluicolortable.h" +#include "llwindow.h" + +#include <algorithm> + static const LLDefaultChildRegistry::Register<LLFlatListView> flat_list_view("flat_list_view"); const LLSD SELECTED_EVENT = LLSD().with("selected", true); @@ -45,17 +53,28 @@ LLFlatListView::Params::Params() multi_select("multi_select"), keep_one_selected("keep_one_selected"), keep_selection_visible_on_reshape("keep_selection_visible_on_reshape",false), + allow_reorder("allow_reorder", false), + drag_indicator_color("drag_indicator_color"), no_items_text("no_items_text") {}; void LLFlatListView::reshape(S32 width, S32 height, bool called_from_parent /* = true */) { S32 delta = height - getRect().getHeight(); + LLRect visible_rc; + LLRect selected_rc = getLastSelectedItemRect(); + bool keep_selection_visible = false; + if (delta != 0 && mKeepSelectionVisibleOnReshape && selected_rc.isValid()) + { + visible_rc = getVisibleContentRect(); + keep_selection_visible = visible_rc.overlaps(selected_rc); + } + LLScrollContainer::reshape(width, height, called_from_parent); setItemsNoScrollWidth(width); rearrangeItems(); - if(delta!= 0 && mKeepSelectionVisibleOnReshape) + if(keep_selection_visible) { ensureSelectedVisible(); } @@ -91,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 @@ -142,7 +161,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); @@ -165,7 +184,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); @@ -217,7 +236,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(); @@ -396,6 +415,11 @@ U32 LLFlatListView::size(const bool only_visible_items) const void LLFlatListView::clear() { + if (mReorderDragPair) + { + cancelReorderDrag(); + } + // This will clear mSelectedItemPairs, calling all appropriate callbacks. resetSelection(); @@ -460,6 +484,16 @@ LLFlatListView::LLFlatListView(const LLFlatListView::Params& p) , mIsConsecutiveSelection(false) , mKeepSelectionVisibleOnReshape(p.keep_selection_visible_on_reshape) , mFocusOnItemClicked(true) + , mAllowReorder(p.allow_reorder) + , mIsReordering(false) + , mReorderDragPair(NULL) + , mDeferredSelectPair(NULL) + , mReorderMouseDownX(0) + , mReorderMouseDownY(0) + , mReorderInsertIndex(-1) + , mDragIndicatorColor(p.drag_indicator_color.isProvided() + ? p.drag_indicator_color() + : LLUIColorTable::instance().getColor("EmphasisColor")) { mBorderThickness = getBorderWidth(); @@ -520,12 +554,29 @@ LLFlatListView::~LLFlatListView() // virtual void LLFlatListView::draw() { + // Keep scrolling while a reorder drag rests over the top/bottom edge, so the + // user does not have to drop, scroll, and grab again. + if (mIsReordering) + { + S32 local_x, local_y; + LLUI::getInstance()->getMousePositionLocal(this, &local_x, &local_y); + if (autoScroll(local_x, local_y)) + { + mReorderInsertIndex = constrainInsertIndex(getInsertIndexAt(local_x, local_y)); + } + } + // Highlight border if a child of this container has keyboard focus if( mSelectedItemsBorder->getVisible() ) { mSelectedItemsBorder->setKeyboardFocusHighlight( hasFocus() ); } LLScrollContainer::draw(); + + if (mIsReordering && mReorderInsertIndex >= 0) + { + drawReorderIndicator(); + } } // virtual @@ -602,7 +653,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; @@ -617,6 +668,11 @@ void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask) setFocus(true); } + if (clicktype == CLICK_LEFT && !(mask & (MASK_SHIFT | MASK_CONTROL))) + { + armReorderDrag(item_pair); + } + bool select_item = !isSelected(item_pair); //*TODO find a better place for that enforcing stuff @@ -695,6 +751,16 @@ void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask) return; } + // When a reorderable list has a multi-selection, a plain click on one of the + // selected rows must not collapse the selection yet: the user may be about to + // drag the whole selection. Defer collapsing to mouse-up if no drag happens. + if (mAllowReorder && mMultipleSelection && !(mask & (MASK_CONTROL | MASK_SHIFT)) + && isSelected(item_pair) && numSelected() > 1) + { + mDeferredSelectPair = item_pair; + return; + } + //no need to do additional commit on selection reset if (!(mask & MASK_CONTROL) || !mMultipleSelection) resetSelection(true); @@ -715,8 +781,364 @@ void LLFlatListView::onItemRightMouseClick(item_pair_t* item_pair, MASK mask) if ( !(mask & MASK_CONTROL) && mMultipleSelection && isSelected(item_pair) ) return; - // else got same behavior as at onItemMouseClick - onItemMouseClick(item_pair, mask); + // else got same behavior as at onItemMouseClick, but a right click must never start a drag + onItemMouseClick(item_pair, mask, CLICK_RIGHT); +} + +static const S32 REORDER_DRAG_THRESHOLD = 5; + +void LLFlatListView::armReorderDrag(item_pair_t* item_pair) +{ + if (!mAllowReorder) + { + return; + } + + if (!item_pair || !item_pair->first) + { + return; + } + + if (size() < 2) + { + return; // nothing to reorder against + } + + if (gFocusMgr.getMouseCapture() && !hasMouseCapture()) + { + return; + } + + mReorderDragPair = item_pair; + mReorderDragGroup.clear(); + mDeferredSelectPair = NULL; + mIsReordering = false; + mReorderInsertIndex = -1; + + LLUI::getInstance()->getMousePositionLocal(this, &mReorderMouseDownX, &mReorderMouseDownY); + gFocusMgr.setMouseCapture(this); +} + +void LLFlatListView::updateReorderDrag(S32 x, S32 y) +{ + if (!mReorderDragPair) return; + + if (!mIsReordering) + { + if (abs(y - mReorderMouseDownY) < REORDER_DRAG_THRESHOLD + && abs(x - mReorderMouseDownX) < REORDER_DRAG_THRESHOLD) + { + return; // still within the click slop, not a drag yet + } + mIsReordering = true; + buildReorderGroup(); + } + + mReorderInsertIndex = constrainInsertIndex(getInsertIndexAt(x, y)); +} + +void LLFlatListView::buildReorderGroup() +{ + mReorderDragGroup.clear(); + + bool drag_selection = mMultipleSelection && isSelected(mReorderDragPair) && numSelected() > 1; + + for (item_pair_t* pair : mItemPairs) + { + if (!pair->first->getVisible()) continue; + + if (pair == mReorderDragPair) + { + mReorderDragGroup.push_back(pair); + } + else if (drag_selection && isSelected(pair) + && (!mReorderValidateCallback || mReorderValidateCallback(mReorderDragPair->second, pair->second))) + { + // only carry along selected rows that belong to the grabbed row's group + mReorderDragGroup.push_back(pair); + } + } +} + +bool LLFlatListView::isInReorderGroup(item_pair_t* item_pair) const +{ + return std::find(mReorderDragGroup.begin(), mReorderDragGroup.end(), item_pair) != mReorderDragGroup.end(); +} + +void LLFlatListView::getReorderRemaining(pairs_list_t& remaining) const +{ + remaining.clear(); + for (item_pair_t* pair : mItemPairs) + { + if (!pair->first->getVisible() || isInReorderGroup(pair)) continue; + remaining.push_back(pair); + } +} + +void LLFlatListView::finishReorderDrag() +{ + if (mReorderDragPair && mIsReordering && mReorderInsertIndex >= 0) + { + pairs_list_t remaining; + getReorderRemaining(remaining); + + // Resolve the drop boundary to the remaining row it lands before (NULL = append). + item_pair_t* anchor = NULL; + S32 cur = 0; + for (item_pair_t* pair : remaining) + { + if (cur == mReorderInsertIndex) { anchor = pair; break; } + ++cur; + } + + LLSD moved_value = mReorderDragPair->second; + + for (item_pair_t* pair : mReorderDragGroup) + { + mItemPairs.remove(pair); + } + + pairs_iterator_t it = (anchor != NULL) + ? std::find(mItemPairs.begin(), mItemPairs.end(), anchor) + : mItemPairs.end(); + for (item_pair_t* pair : mReorderDragGroup) + { + mItemPairs.insert(it, pair); // preserves group order; it stays before anchor + } + + rearrangeItems(); + + if (mReorderCallback) + { + 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); + } + } + + cancelReorderDrag(); +} + +void LLFlatListView::cancelReorderDrag() +{ + if (hasMouseCapture()) + { + gFocusMgr.setMouseCapture(NULL); + } + + clearReorderDragState(); +} + +void LLFlatListView::clearReorderDragState() +{ + if (mIsReordering) + { + getWindow()->setCursor(UI_CURSOR_ARROW); + } + + bool was_armed = (mReorderDragPair != NULL); + + mReorderDragPair = NULL; + mReorderDragGroup.clear(); + mDeferredSelectPair = NULL; + mIsReordering = false; + mReorderInsertIndex = -1; + + if (was_armed && mReorderEndedCallback) + { + mReorderEndedCallback(); + } +} + +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; + localPointToOtherView(x, y, &panel_x, &panel_y, mItemsPanel); + + // The drop boundary among the remaining rows is the count of remaining rows + // whose vertical centre sits above the cursor. + S32 index = 0; + for (item_pair_t* pair : mItemPairs) + { + if (!pair->first->getVisible() || isInReorderGroup(pair)) continue; + + if (pair->first->getRect().getCenterY() > panel_y) + { + ++index; + } + } + 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 NULL; +} + +S32 LLFlatListView::constrainInsertIndex(S32 dest_index) const +{ + 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. + pairs_list_t remaining; + getReorderRemaining(remaining); + + const LLSD& dragged = mReorderDragPair->second; + S32 first_valid = -1; + S32 last_valid = -1; + S32 i = 0; + for (item_pair_t* pair : remaining) + { + if (mReorderValidateCallback(dragged, pair->second)) + { + 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 (dest_index < first_valid) return first_valid; + if (dest_index > last_valid + 1) return last_valid + 1; + return dest_index; +} + +void LLFlatListView::drawReorderIndicator() +{ + pairs_list_t remaining; + getReorderRemaining(remaining); + if (remaining.empty()) return; + + const LLRect& panel_rc = mItemsPanel->getRect(); + const LLColor4& color = mDragIndicatorColor.get(); + + // faint highlight on each row being moved + for (item_pair_t* pair : mReorderDragGroup) + { + const LLRect& dr = pair->first->getRect(); + gl_rect_2d(panel_rc.mLeft + dr.mLeft, panel_rc.mBottom + dr.mTop, + panel_rc.mLeft + dr.mRight, panel_rc.mBottom + dr.mBottom, + color % 0.15f, true); + } + + // insertion line at the drop boundary among the remaining rows + S32 count = (S32)remaining.size(); + bool at_end = mReorderInsertIndex >= count; + S32 anchor_idx = at_end ? count - 1 : mReorderInsertIndex; + + item_pair_t* anchor = NULL; + S32 cur = 0; + for (item_pair_t* pair : remaining) + { + if (cur == anchor_idx) { anchor = pair; break; } + ++cur; + } + 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; + + gl_rect_2d(left, line_y, right, line_y - 1, color, true); +} + +bool LLFlatListView::handleHover(S32 x, S32 y, MASK mask) +{ + if (mAllowReorder && mReorderDragPair && hasMouseCapture()) + { + updateReorderDrag(x, y); + if (mIsReordering) + { + getWindow()->setCursor(UI_CURSOR_ARROWDRAG); + return true; + } + } + 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 != NULL); +} + +bool LLFlatListView::handleMouseUp(S32 x, S32 y, MASK mask) +{ + if (mReorderDragPair && hasMouseCapture()) + { + bool was_reordering = mIsReordering; + item_pair_t* deferred = mDeferredSelectPair; + mDeferredSelectPair = NULL; + + finishReorderDrag(); + + if (was_reordering) + { + return true; // a drag happened: keep the (multi-)selection on the moved rows + } + + // No drag: apply the selection collapse that was deferred on mouse-down. + if (deferred) + { + resetSelection(true); + selectItemPair(deferred, true); + return true; + } + } + return LLScrollContainer::handleMouseUp(x, y, mask); } bool LLFlatListView::handleKeyHere(KEY key, MASK mask) @@ -1087,6 +1509,18 @@ bool LLFlatListView::removeItemPair(item_pair_t* item_pair, bool rearrange) { llassert(item_pair); + bool removing_reorder_pair = item_pair == mReorderDragPair + || std::find(mReorderDragGroup.begin(), mReorderDragGroup.end(), item_pair) != mReorderDragGroup.end(); + + if (removing_reorder_pair) + { + cancelReorderDrag(); + } + if (item_pair == mDeferredSelectPair) + { + mDeferredSelectPair = NULL; + } + bool deleted = false; bool selection_changed = false; for (pairs_iterator_t it = mItemPairs.begin(); it != mItemPairs.end(); ++it) diff --git a/indra/llui/llflatlistview.h b/indra/llui/llflatlistview.h index 39afa33be8..ae20a4b1eb 100644 --- a/indra/llui/llflatlistview.h +++ b/indra/llui/llflatlistview.h @@ -30,6 +30,7 @@ #include "llpanel.h" #include "llscrollcontainer.h" #include "lltextbox.h" +#include "lluicolor.h" /** @@ -106,12 +107,28 @@ public: /** padding between items */ Optional<U32> item_pad; + /** allow items to be reordered by dragging them with the mouse */ + Optional<bool> allow_reorder; + + /** colour of the insertion indicator drawn while reordering */ + Optional<LLUIColor> drag_indicator_color; + /** textbox with info message when list is empty*/ Optional<LLTextBox::Params> no_items_text; Params(); }; + /** Fired after the user drags an item to a new position: (moved value, new visible index). */ + typedef boost::function<void(const LLSD& value, S32 new_index)> reorder_signal_t; + + /** + * Returns true if the dragged item is allowed to move past the given neighbour. + * Lets owners constrain reordering (e.g. keep items within a group). Optional; + * when unset any reorder is permitted. + */ + typedef boost::function<bool(const LLSD& dragged, const LLSD& neighbour)> reorder_validate_signal_t; + // disable traversal when finding widget to hand focus off to /*virtual*/ bool canFocusChildren() const override { return false; } @@ -259,6 +276,18 @@ public: /** Turn on/off selection support */ void setAllowSelection(bool can_select) { mAllowSelection = can_select; } + /** Turn on/off drag-to-reorder support */ + void setAllowReorder(bool allow) { mAllowReorder = allow; } + + 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<void()> cb) { mReorderEndedCallback = cb; } + + /** True while an item is grabbed for a drag-to-reorder (before or during the drag) */ + bool isReorderActive() const { return mReorderDragPair != NULL; } + /** 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; } @@ -330,7 +359,7 @@ protected: 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); @@ -368,6 +397,16 @@ protected: virtual bool handleKeyHere(KEY key, MASK mask) override; + 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; @@ -384,6 +423,29 @@ protected: private: + // Drag-to-reorder helpers. + void armReorderDrag(item_pair_t* item_pair); + void updateReorderDrag(S32 x, S32 y); + void finishReorderDrag(); + void cancelReorderDrag(); + void clearReorderDragState(); + + // Builds the set of pairs being dragged (the whole selection when the grabbed + // row is part of a multi-selection, constrained to the validator's group). + void buildReorderGroup(); + bool isInReorderGroup(item_pair_t* item_pair) const; + + // The remaining visible pairs (those not being dragged), in visual order. + 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(); + void setItemsNoScrollWidth(S32 new_width) {mItemsNoScrollWidth = new_width - 2 * mBorderThickness;} void setNoItemsCommentVisible(bool visible) const; @@ -427,6 +489,20 @@ private: bool mFocusOnItemClicked; + /** Drag-to-reorder state */ + bool mAllowReorder; + bool mIsReordering; // a drag is currently in progress + 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 + S32 mReorderMouseDownX; + S32 mReorderMouseDownY; + S32 mReorderInsertIndex; // current drop boundary among the remaining (non-dragged) items + LLUIColor mDragIndicatorColor; + reorder_signal_t mReorderCallback; + reorder_validate_signal_t mReorderValidateCallback; + boost::function<void()> mReorderEndedCallback; + /** All pairs of the list */ pairs_list_t mItemPairs; diff --git a/indra/llui/llscrollcontainer.cpp b/indra/llui/llscrollcontainer.cpp index e36fd45bb4..ad2f58db69 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,8 +94,12 @@ 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; + mStoredDocPos[HORIZONTAL] = 0; + static LLUICachedControl<S32> scrollbar_size_control ("UIScrollbarSize", 0); S32 scrollbar_size = (mSize == -1 ? scrollbar_size_control : mSize); @@ -605,6 +610,22 @@ void LLScrollContainer::updateScroll() calcVisibleSize( &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); S32 border_width = getBorderWidth(); + + // 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 ) { if( doc_rect.mTop < getRect().getHeight() - border_width ) @@ -673,6 +694,18 @@ void LLScrollContainer::updateScroll() 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 859750d71c..7761824dbb 100644 --- a/indra/llui/llscrollcontainer.h +++ b/indra/llui/llscrollcontainer.h @@ -64,7 +64,8 @@ public: reserve_scroll_corner, border_visible, hide_scrollbar, - ignore_arrow_keys; + ignore_arrow_keys, + keep_scroll_pos; Optional<F32> min_auto_scroll_rate, max_auto_scroll_rate; Optional<U32> max_auto_scroll_zone; @@ -138,6 +139,8 @@ private: void calcVisibleSize( S32 *visible_width, S32 *visible_height, bool* show_h_scrollbar, bool* show_v_scrollbar ) const; LLScrollbar* mScrollbar[ORIENTATION_COUNT]; + S32 mStoredDocPos[ORIENTATION_COUNT]; + bool mKeepScrollPos; S32 mSize; bool mIsOpaque; LLUIColor mBackgroundColor; |
