diff --git a/indra/llui/llbutton.cpp b/indra/llui/llbutton.cpp index daa7de213..1b9add0a4 100644 --- a/indra/llui/llbutton.cpp +++ b/indra/llui/llbutton.cpp @@ -272,7 +272,7 @@ void LLButton::onCommit() LLUICtrl::onCommit(); } -/*boost::signals2::connection LLButton::setClickedCallback(const CommitCallbackParam& cb) +boost::signals2::connection LLButton::setClickedCallback(const CommitCallbackParam& cb) { return setClickedCallback(initCommitCallback(cb)); } @@ -287,7 +287,7 @@ boost::signals2::connection LLButton::setMouseUpCallback(const CommitCallbackPar boost::signals2::connection LLButton::setHeldDownCallback(const CommitCallbackParam& cb) { return setHeldDownCallback(initCommitCallback(cb)); -}*/ +} boost::signals2::connection LLButton::setClickedCallback( const commit_signal_t::slot_type& cb ) diff --git a/indra/llui/llbutton.h b/indra/llui/llbutton.h index 219b0dfec..1495740c6 100644 --- a/indra/llui/llbutton.h +++ b/indra/llui/llbutton.h @@ -116,10 +116,10 @@ public: void setSelectedLabelColor( const LLColor4& c ) { mSelectedLabelColor = c; } - /*boost::signals2::connection setClickedCallback(const CommitCallbackParam& cb); + boost::signals2::connection setClickedCallback(const CommitCallbackParam& cb); boost::signals2::connection setMouseDownCallback(const CommitCallbackParam& cb); boost::signals2::connection setMouseUpCallback(const CommitCallbackParam& cb); - boost::signals2::connection setHeldDownCallback(const CommitCallbackParam& cb);*/ + boost::signals2::connection setHeldDownCallback(const CommitCallbackParam& cb); boost::signals2::connection setClickedCallback( const commit_signal_t::slot_type& cb ); // mouse down and up within button boost::signals2::connection setMouseDownCallback( const commit_signal_t::slot_type& cb ); diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index 27df264af..338370e8f 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -486,7 +486,7 @@ BOOL LLMenuItemGL::handleMouseDown( S32 x, S32 y, MASK mask) BOOL LLMenuItemGL::handleScrollWheel( S32 x, S32 y, S32 clicks ) { // If the menu is scrollable let it handle the wheel event. - return FALSE;//!getMenu()->isScrollable(); + return !getMenu()->isScrollable(); } void LLMenuItemGL::draw( void ) @@ -1805,6 +1805,117 @@ void LLMenuItemBranchDownGL::draw( void ) setHover(FALSE); } + +class LLMenuScrollItem : public LLMenuItemCallGL +{ +public: + enum EArrowType + { + ARROW_DOWN, + ARROW_UP + }; + struct ArrowTypes : public LLInitParam::TypeValuesHelper + { + static void declareValues() + { + declare("up", ARROW_UP); + declare("down", ARROW_DOWN); + } + }; + + struct Params : public LLInitParam::Block + { + Optional arrow_type; + Optional scroll_callback; + }; + +protected: + LLMenuScrollItem(const Params&); + friend class LLUICtrlFactory; + +public: + /*virtual*/ void draw(); + /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent); + /*virtual*/ void setEnabled(BOOL enabled); + virtual void doIt( void ); + +private: + LLButton* mArrowBtn; +}; + +LLMenuScrollItem::LLMenuScrollItem(const Params& p) +: LLMenuItemCallGL(p.name, NULL/*p*/) +{ + std::string icon; + if (p.arrow_type.isProvided() && p.arrow_type == ARROW_UP) + { + icon = "arrow_up.tga"; + } + else + { + icon = "arrow_down.tga"; + } + + /* Singu TODO: LLButton::Params + LLButton::Params bparams; + + // Disabled the Return key handling by LLMenuScrollItem instead of + // passing the key press to the currently selected menu item. See STORM-385. + bparams.commit_on_return(false); + bparams.mouse_opaque(true); + bparams.scale_image(false); + bparams.click_callback(p.scroll_callback); + bparams.mouse_held_callback(p.scroll_callback); + bparams.follows.flags(FOLLOWS_ALL); + std::string background = "transparent.j2c"; + bparams.image_unselected.name(background); + bparams.image_disabled.name(background); + bparams.image_selected.name(background); + bparams.image_hover_selected.name(background); + bparams.image_disabled_selected.name(background); + bparams.image_hover_unselected.name(background); + bparams.image_overlay.name(icon); + + mArrowBtn = LLUICtrlFactory::create(bparams); + */ + const LLRect rect = getRect(); + const std::string background = "transparent.j2c"; + mArrowBtn = new LLButton("", LLRect(0, 0, rect.getWidth(), rect.getHeight()), background, background, "", NULL); + mArrowBtn->setCommitOnReturn(false); + mArrowBtn->setMouseOpaque(true); + mArrowBtn->setScaleImage(false); + mArrowBtn->setClickedCallback(p.scroll_callback); + mArrowBtn->setHeldDownCallback(p.scroll_callback); + mArrowBtn->setFollows(FOLLOWS_ALL); + mArrowBtn->setImageOverlay(icon); + addChild(mArrowBtn); +} + +/*virtual*/ +void LLMenuScrollItem::draw() +{ + LLUICtrl::draw(); +} + +/*virtual*/ +void LLMenuScrollItem::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + mArrowBtn->reshape(width, height, called_from_parent); + LLView::reshape(width, height, called_from_parent); +} + +/*virtual*/ +void LLMenuScrollItem::setEnabled(BOOL enabled) +{ + mArrowBtn->setEnabled(enabled); + LLView::setEnabled(enabled); +} + +void LLMenuScrollItem::doIt( void ) +{ + LLUICtrl::onCommit(); +} + ///============================================================================ /// Class LLMenuGL ///============================================================================ @@ -1816,10 +1927,12 @@ LLMenuGL::LLMenuGL( const std::string& name, const std::string& label ) : LLUICtrl( name, LLRect(), FALSE), mBackgroundColor( sDefaultBackgroundColor ), mBgVisible( TRUE ), - mHasSelection( FALSE ), + mHasSelection(false), + mHorizontalLayout(false), + mScrollable(mHorizontalLayout ? FALSE : /*p.scrollable*/false), // Scrolling is supported only for vertical layout + mMaxScrollableItems(/*p.max_scrollable_items*/ U32_MAX), mLabel( label ), mDropShadowed( TRUE ), - mHorizontalLayout( FALSE ), mKeepFixedSize( FALSE ), mLastMouseX(0), mLastMouseY(0), @@ -1829,9 +1942,12 @@ LLMenuGL::LLMenuGL( const std::string& name, const std::string& label ) mTearOffItem(NULL), mSpilloverBranch(NULL), mFirstVisibleItem(NULL), + mArrowUpItem(NULL), + mArrowDownItem(NULL), mSpilloverMenu(NULL), mJumpKey(KEY_NONE), mNeedsArrange(FALSE), + mResetScrollPositionOnShow(true), mShortcutPad(ACCEL_PAD_PIXELS) { mFadeTimer.stop(); @@ -1842,10 +1958,12 @@ LLMenuGL::LLMenuGL( const std::string& label) : LLUICtrl( label, LLRect(), FALSE), mBackgroundColor( sDefaultBackgroundColor ), mBgVisible( TRUE ), - mHasSelection( FALSE ), + mHasSelection(false), + mHorizontalLayout(false), + mScrollable(mHorizontalLayout ? FALSE : /*p.scrollable*/false), // Scrolling is supported only for vertical layout + mMaxScrollableItems(/*p.max_scrollable_items*/ U32_MAX), mLabel( label ), mDropShadowed( TRUE ), - mHorizontalLayout( FALSE ), mKeepFixedSize( FALSE ), mLastMouseX(0), mLastMouseY(0), @@ -1856,8 +1974,11 @@ LLMenuGL::LLMenuGL( const std::string& label) mSpilloverBranch(NULL), mSpilloverMenu(NULL), mFirstVisibleItem(NULL), + mArrowUpItem(NULL), + mArrowDownItem(NULL), mJumpKey(KEY_NONE), mNeedsArrange(FALSE), + mResetScrollPositionOnShow(true), mShortcutPad(ACCEL_PAD_PIXELS) { mFadeTimer.stop(); @@ -2361,6 +2482,13 @@ LLView* LLMenuGL::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *fa menu->parseChildXML(child, parent, factory); } + if (node->hasAttribute("scrollable")) + { + bool b; + node->getAttribute_bool("scrollable", b); + setScrollable(b); + } + if (create_jump_keys) { menu->createJumpKeys(); @@ -2369,6 +2497,117 @@ LLView* LLMenuGL::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *fa } + +bool LLMenuGL::scrollItems(EScrollingDirection direction) +{ + // Slowing down items scrolling when arrow button is held + if (mScrollItemsTimer.hasExpired() && NULL != mFirstVisibleItem) + { + mScrollItemsTimer.setTimerExpirySec(.033f); + } + else + { + return false; + } + + switch (direction) + { + case SD_UP: + { + item_list_t::iterator cur_item_iter; + item_list_t::iterator prev_item_iter; + for (cur_item_iter = mItems.begin(), prev_item_iter = mItems.begin(); cur_item_iter != mItems.end(); cur_item_iter++) + { + if( (*cur_item_iter) == mFirstVisibleItem) + { + break; + } + if ((*cur_item_iter)->getVisible()) + { + prev_item_iter = cur_item_iter; + } + } + + if ((*prev_item_iter)->getVisible()) + { + mFirstVisibleItem = *prev_item_iter; + } + break; + } + case SD_DOWN: + { + if (NULL == mFirstVisibleItem) + { + mFirstVisibleItem = *mItems.begin(); + } + + item_list_t::iterator cur_item_iter; + + for (cur_item_iter = mItems.begin(); cur_item_iter != mItems.end(); cur_item_iter++) + { + if( (*cur_item_iter) == mFirstVisibleItem) + { + break; + } + } + + item_list_t::iterator next_item_iter; + + if (cur_item_iter != mItems.end()) + { + for (next_item_iter = ++cur_item_iter; next_item_iter != mItems.end(); next_item_iter++) + { + if( (*next_item_iter)->getVisible()) + { + break; + } + } + + if (next_item_iter != mItems.end() && + (*next_item_iter)->getVisible()) + { + mFirstVisibleItem = *next_item_iter; + } + } + break; + } + case SD_BEGIN: + { + mFirstVisibleItem = *mItems.begin(); + break; + } + case SD_END: + { + item_list_t::reverse_iterator first_visible_item_iter = mItems.rend(); + + // Need to scroll through number of actual existing items in menu. + // Otherwise viewer will hang for a time needed to scroll U32_MAX + // times in std::advance(). STORM-659. + size_t nitems = mItems.size(); + U32 scrollable_items = nitems < mMaxScrollableItems ? nitems : mMaxScrollableItems; + + // Advance by mMaxScrollableItems back from the end of the list + // to make the last item visible. + std::advance(first_visible_item_iter, scrollable_items); + mFirstVisibleItem = *first_visible_item_iter; + break; + } + default: + //LL_WARNS() << "Unknown scrolling direction: " << direction << LL_ENDL; + llwarns << "Unknown scrolling direction: " << direction << llendl; + } + + mNeedsArrange = TRUE; + arrangeAndClear(); + + return true; +} + +void LLMenuGL::setScrollable(bool b) +{ + mScrollable = b; +} + // rearrange the child rects so they fit the shape of the menu. void LLMenuGL::arrange( void ) { @@ -2402,9 +2641,10 @@ void LLMenuGL::arrange( void ) // Scrolling support item_list_t::iterator first_visible_item_iter; - //S32 height_before_first_visible_item = -1; - //S32 visible_items_height = 0; - //U32 scrollable_items_cnt = 0; + item_list_t::iterator first_hidden_item_iter = mItems.end(); + S32 height_before_first_visible_item = -1; + S32 visible_items_height = 0; + U32 scrollable_items_cnt = 0; if (mHorizontalLayout) { @@ -2452,15 +2692,16 @@ void LLMenuGL::arrange( void ) else { item_list_t::iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) { - llassert_always((*item_iter)!=NULL); // do first so LLMenuGLItemCall can call on_visible to determine if visible (*item_iter)->buildDrawLabel(); if ((*item_iter)->getVisible()) { if (!getTornOff() + && !mScrollable && *item_iter != mSpilloverBranch && height + (*item_iter)->getNominalHeight() > max_height - spillover_item_height) { @@ -2476,6 +2717,8 @@ void LLMenuGL::arrange( void ) removeChild(itemp); mSpilloverMenu->addChild(itemp); } + + addChild(mSpilloverBranch); height += mSpilloverBranch->getNominalHeight(); @@ -2489,20 +2732,170 @@ void LLMenuGL::arrange( void ) height += (*item_iter)->getNominalHeight(); width = llmax( width, (*item_iter)->getNominalWidth() ); } + + if (mScrollable) + { + // Determining visible items boundaries + if (NULL == mFirstVisibleItem) + { + mFirstVisibleItem = *item_iter; + } + + if (*item_iter == mFirstVisibleItem) + { + height_before_first_visible_item = height - (*item_iter)->getNominalHeight(); + first_visible_item_iter = item_iter; + scrollable_items_cnt = 0; + } + + if (-1 != height_before_first_visible_item && 0 == visible_items_height && + (++scrollable_items_cnt > mMaxScrollableItems || + height - height_before_first_visible_item > max_height - spillover_item_height * 2 )) + { + first_hidden_item_iter = item_iter; + visible_items_height = height - height_before_first_visible_item - (*item_iter)->getNominalHeight(); + scrollable_items_cnt--; + } + } + } + } + + if (mScrollable) + { + S32 max_items_height = max_height - spillover_item_height * 2; + + if (visible_items_height == 0) + visible_items_height = height - height_before_first_visible_item; + + // Fix mFirstVisibleItem value, if it doesn't allow to display all items, that can fit + if (visible_items_height < max_items_height && scrollable_items_cnt < mMaxScrollableItems) + { + item_list_t::iterator tmp_iter(first_visible_item_iter); + while (visible_items_height < max_items_height && + scrollable_items_cnt < mMaxScrollableItems && + first_visible_item_iter != mItems.begin()) + { + if ((*first_visible_item_iter)->getVisible()) + { + // It keeps visible item, after first_visible_item_iter + tmp_iter = first_visible_item_iter; + } + + first_visible_item_iter--; + + if ((*first_visible_item_iter)->getVisible()) + { + visible_items_height += (*first_visible_item_iter)->getNominalHeight(); + height_before_first_visible_item -= (*first_visible_item_iter)->getNominalHeight(); + scrollable_items_cnt++; + } + } + + // Roll back one item, that doesn't fit + if (visible_items_height > max_items_height) + { + visible_items_height -= (*first_visible_item_iter)->getNominalHeight(); + height_before_first_visible_item += (*first_visible_item_iter)->getNominalHeight(); + scrollable_items_cnt--; + first_visible_item_iter = tmp_iter; + } + if (!(*first_visible_item_iter)->getVisible()) + { + first_visible_item_iter = tmp_iter; + } + + mFirstVisibleItem = *first_visible_item_iter; } } } S32 cur_height = (S32)llmin(max_height, height); + + if (mScrollable && + (height_before_first_visible_item > MENU_ITEM_PADDING || + height_before_first_visible_item + visible_items_height < (S32)height)) + { + // Reserving 2 extra slots for arrow items + cur_height = visible_items_height + spillover_item_height * 2; + } + setRect(LLRect(getRect().mLeft, getRect().mTop, getRect().mLeft + width, getRect().mTop - cur_height)); S32 cur_width = 0; S32 offset = 0; + if (mScrollable) + { + // No space for all items, creating arrow items + if (height_before_first_visible_item > MENU_ITEM_PADDING || + height_before_first_visible_item + visible_items_height < (S32)height) + { + if (NULL == mArrowUpItem) + { + LLMenuScrollItem::Params item_params; + item_params.name(ARROW_UP); + item_params.arrow_type(LLMenuScrollItem::ARROW_UP); + item_params.scroll_callback.function(boost::bind(&LLMenuGL::scrollItems, this, SD_UP)); + + mArrowUpItem = LLUICtrlFactory::create(item_params); + LLUICtrl::addChild(mArrowUpItem); + + } + if (NULL == mArrowDownItem) + { + LLMenuScrollItem::Params item_params; + item_params.name(ARROW_DOWN); + item_params.arrow_type(LLMenuScrollItem::ARROW_DOWN); + item_params.scroll_callback.function(boost::bind(&LLMenuGL::scrollItems, this, SD_DOWN)); + + mArrowDownItem = LLUICtrlFactory::create(item_params); + LLUICtrl::addChild(mArrowDownItem); + } + + LLRect rect; + mArrowUpItem->setRect(rect.setLeftTopAndSize( 0, cur_height, width, mArrowUpItem->getNominalHeight())); + mArrowUpItem->setVisible(TRUE); + mArrowUpItem->setEnabled(height_before_first_visible_item > MENU_ITEM_PADDING); + mArrowUpItem->reshape(width, mArrowUpItem->getNominalHeight()); + mArrowDownItem->setRect(rect.setLeftTopAndSize( 0, mArrowDownItem->getNominalHeight(), width, mArrowDownItem->getNominalHeight())); + mArrowDownItem->setVisible(TRUE); + mArrowDownItem->setEnabled(height_before_first_visible_item + visible_items_height < (S32)height); + mArrowDownItem->reshape(width, mArrowDownItem->getNominalHeight()); + + cur_height -= mArrowUpItem->getNominalHeight(); + + offset = menu_region_rect.mRight; // This moves items behind visible area + } + else + { + if (NULL != mArrowUpItem) + { + mArrowUpItem->setVisible(FALSE); + } + if (NULL != mArrowDownItem) + { + mArrowDownItem->setVisible(FALSE); + } + } + + } + item_list_t::iterator item_iter; for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) { if ((*item_iter)->getVisible()) { + if (mScrollable) + { + if (item_iter == first_visible_item_iter) + { + offset = 0; + } + else if (item_iter == first_hidden_item_iter) + { + offset = menu_region_rect.mRight; // This moves items behind visible area + } + } + // setup item rect to hold label LLRect rect; if (mHorizontalLayout) @@ -2683,6 +3076,8 @@ void LLMenuGL::empty( void ) mItems.clear(); mFirstVisibleItem = NULL; + mArrowUpItem = NULL; + mArrowDownItem = NULL; deleteAllChildren(); @@ -2899,9 +3294,36 @@ LLMenuItemGL* LLMenuGL::highlightNextItem(LLMenuItemGL* cur_item, BOOL skip_disa next_item_iter = cur_item_iter; next_item_iter++; + // First visible item position in the items list + item_list_t::iterator first_visible_item_iter = std::find(mItems.begin(), mItems.end(), mFirstVisibleItem); + if (next_item_iter == mItems.end()) { next_item_iter = mItems.begin(); + + // If current item is the last in the list, the menu is scrolled to the beginning + // and the first item is highlighted. + if (mScrollable && !scrollItems(SD_BEGIN)) + { + return NULL; + } + } + // If current item is the last visible, the menu is scrolled one item down + // and the next item is highlighted. + else if (mScrollable && + (U32)std::abs(std::distance(first_visible_item_iter, next_item_iter)) >= mMaxScrollableItems) + { + // Call highlightNextItem() recursively only if the menu was successfully scrolled down. + // If scroll timer hasn't expired yet the menu won't be scrolled and calling + // highlightNextItem() will result in an endless recursion. + if (scrollItems(SD_DOWN)) + { + return highlightNextItem(cur_item, skip_disabled); + } + else + { + return NULL; + } } } @@ -2974,9 +3396,36 @@ LLMenuItemGL* LLMenuGL::highlightPrevItem(LLMenuItemGL* cur_item, BOOL skip_disa prev_item_iter = cur_item_iter; prev_item_iter++; + // First visible item reverse position in the items list + item_list_t::reverse_iterator first_visible_item_iter = std::find(mItems.rbegin(), mItems.rend(), mFirstVisibleItem); + if (prev_item_iter == mItems.rend()) { prev_item_iter = mItems.rbegin(); + + // If current item is the first in the list, the menu is scrolled to the end + // and the last item is highlighted. + if (mScrollable && !scrollItems(SD_END)) + { + return NULL; + } + } + // If current item is the first visible, the menu is scrolled one item up + // and the previous item is highlighted. + else if (mScrollable && + std::distance(first_visible_item_iter, cur_item_iter) <= 0) + { + // Call highlightNextItem() only if the menu was successfully scrolled up. + // If scroll timer hasn't expired yet the menu won't be scrolled and calling + // highlightNextItem() will result in an endless recursion. + if (scrollItems(SD_UP)) + { + return highlightPrevItem(cur_item, skip_disabled); + } + else + { + return NULL; + } } } @@ -3141,7 +3590,21 @@ BOOL LLMenuGL::handleHover( S32 x, S32 y, MASK mask ) BOOL LLMenuGL::handleScrollWheel( S32 x, S32 y, S32 clicks ) { - return blockMouseEvent(x, y); + if (!mScrollable) + return blockMouseEvent(x, y); + + if( clicks > 0 ) + { + while( clicks-- ) + scrollItems(SD_DOWN); + } + else + { + while( clicks++ ) + scrollItems(SD_UP); + } + + return TRUE; } void LLMenuGL::draw( void ) diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h index 9fd6868b7..dd4c40e53 100644 --- a/indra/llui/llmenugl.h +++ b/indra/llui/llmenugl.h @@ -453,6 +453,15 @@ public: static const std::string ARROW_UP; static const std::string ARROW_DOWN; + // for scrollable menus + typedef enum e_scrolling_direction + { + SD_UP = 0, + SD_DOWN = 1, + SD_BEGIN = 2, + SD_END = 3 + } EScrollingDirection; + protected: // let branching menu items use my protected traversal methods friend class LLMenuItemBranchGL; @@ -566,15 +575,18 @@ public: static BOOL getKeyboardMode() { return sKeyboardMode; } S32 getShortcutPad() { return mShortcutPad; } - BOOL isScrollable() const { return FALSE; } + + bool scrollItems(EScrollingDirection direction); + BOOL isScrollable() const { return mScrollable; } + void setScrollable(bool b); static class LLMenuHolderGL* sMenuContainer; - bool isScrollPositionOnShowReset() { return false; } + void resetScrollPositionOnShow(bool reset_scroll_pos) { mResetScrollPositionOnShow = reset_scroll_pos; } + bool isScrollPositionOnShowReset() { return mResetScrollPositionOnShow; } protected: void createSpilloverBranch(); void cleanupSpilloverBranch(); - // Add the menu item to this menu. virtual BOOL append( LLMenuItemGL* item ); BOOL append(LLMenuItemGL* item, LLView* insert_before); @@ -587,6 +599,7 @@ protected: typedef std::list< LLMenuItemGL* > item_list_t; item_list_t mItems; LLMenuItemGL*mFirstVisibleItem; + LLMenuItemGL *mArrowUpItem, *mArrowDownItem; typedef std::map navigation_key_map_t; navigation_key_map_t mJumpKeys; @@ -594,11 +607,15 @@ protected: S32 mLastMouseY; S32 mMouseVelX; S32 mMouseVelY; + U32 mMaxScrollableItems; BOOL mHorizontalLayout; + BOOL mScrollable; BOOL mKeepFixedSize; BOOL mNeedsArrange; private: + + static LLColor4 sDefaultBackgroundColor; static BOOL sKeyboardMode; @@ -609,12 +626,14 @@ private: BOOL mDropShadowed; // Whether to drop shadow bool mHasSelection; LLFrameTimer mFadeTimer; + LLTimer mScrollItemsTimer; BOOL mTornOff; class LLMenuItemTearOffGL* mTearOffItem; class LLMenuItemBranchGL* mSpilloverBranch; LLMenuGL* mSpilloverMenu; KEY mJumpKey; S32 mShortcutPad; + bool mResetScrollPositionOnShow; }; // end class LLMenuGL