From dceb1a64fe60fdf5f9e18e922a2759c82993596c Mon Sep 17 00:00:00 2001 From: Shyotl Date: Thu, 13 Jun 2013 04:33:49 -0500 Subject: [PATCH] Added mouse entry/mouse leave ui callback support. Works manually or through CommitCallbackRegistry (*.mouseenter_callback & *.mouseleave_callback attributes) --- indra/llui/lluictrl.cpp | 67 ++++++++++++++-- indra/llui/lluictrl.h | 9 ++- indra/llui/llview.cpp | 70 +++++++++++++++++ indra/llui/llview.h | 28 +++++++ indra/newview/llviewerwindow.cpp | 128 ++++++++++++++++++++++++++++++- indra/newview/llviewerwindow.h | 2 + 6 files changed, 294 insertions(+), 10 deletions(-) diff --git a/indra/llui/lluictrl.cpp b/indra/llui/lluictrl.cpp index 3fcd5ab1d..625aa7eab 100644 --- a/indra/llui/lluictrl.cpp +++ b/indra/llui/lluictrl.cpp @@ -48,6 +48,8 @@ LLUICtrl::LLUICtrl() : mCommitCallback(NULL), mValidateCallback(NULL), mCallbackUserData(NULL), + mMouseEnterSignal(NULL), + mMouseLeaveSignal(NULL), mTentative(FALSE), mTabStop(TRUE), mIsChrome(FALSE) @@ -66,6 +68,8 @@ LLUICtrl::LLUICtrl(const std::string& name, const LLRect rect, BOOL mouse_opaque mViewModel(LLViewModelPtr(new LLViewModel)), mValidateCallback( NULL ), mCallbackUserData( NULL ), + mMouseEnterSignal(NULL), + mMouseLeaveSignal(NULL), mTentative( FALSE ), mTabStop( TRUE ), mIsChrome(FALSE) @@ -88,6 +92,25 @@ LLUICtrl::~LLUICtrl() delete mValidateSignal; } + +// virtual +void LLUICtrl::onMouseEnter(S32 x, S32 y, MASK mask) +{ + if (mMouseEnterSignal) + { + (*mMouseEnterSignal)(this, getValue()); + } +} + +// virtual +void LLUICtrl::onMouseLeave(S32 x, S32 y, MASK mask) +{ + if(mMouseLeaveSignal) + { + (*mMouseLeaveSignal)(this, getValue()); + } +} + void LLUICtrl::onCommit() { if( mCommitCallback ) @@ -470,20 +493,36 @@ void LLUICtrl::initFromXML(LLXMLNodePtr node, LLView* parent) std::string str = node->getName()->mString; std::string attrib_str; LLXMLNodePtr child_node; - if(node->getChild((str+".commit_callback").c_str(),child_node,false)) + + //Since so many other callback 'types' piggyback off of the commitcallback registrar as well as use the same callback signature + //we can assemble a nice little static list to iterate down instead of copy-pasting mostly similar code for each instance. + //Validate/enable callbacks differ, as it uses its own registry/callback signature. This could be worked around with a template, but keeping + //all the code local to this scope is more beneficial. + typedef boost::signals2::connection (LLUICtrl::*commit_fn)( const commit_signal_t::slot_type& cb ); + static std::pair sCallbackRegistryMap[3] = { - if(child_node->getAttributeString("function",attrib_str)) + std::pair(".commit_callback",&LLUICtrl::setCommitCallback), + std::pair(".mouseenter_callback",&LLUICtrl::setMouseEnterCallback), + std::pair(".mouseleave_callback",&LLUICtrl::setMouseLeaveCallback) + }; + for(S32 i= 0; i < sizeof(sCallbackRegistryMap)/sizeof(sCallbackRegistryMap[0]);++i) + { + if(node->getChild((str+sCallbackRegistryMap[i].first).c_str(),child_node,false)) { - commit_callback_t* func = (CommitCallbackRegistry::getValue(attrib_str)); - if (func) + if(child_node->getAttributeString("function",attrib_str)) { - if(child_node->getAttributeString("parameter",attrib_str)) - setCommitCallback(boost::bind((*func), this, LLSD(attrib_str))); - else - setCommitCallback(commit_signal_t::slot_type(*func)); + commit_callback_t* func = (CommitCallbackRegistry::getValue(attrib_str)); + if (func) + { + if(child_node->getAttributeString("parameter",attrib_str)) + (this->*sCallbackRegistryMap[i].second)(boost::bind((*func), this, LLSD(attrib_str))); + else + (this->*sCallbackRegistryMap[i].second)(commit_signal_t::slot_type(*func)); + } } } } + if(node->getChild((str+".validate_callback").c_str(),child_node,false)) { if(child_node->getAttributeString("function",attrib_str)) @@ -587,3 +626,15 @@ boost::signals2::connection LLUICtrl::setValidateCallback( const enable_signal_t if (!mValidateSignal) mValidateSignal = new enable_signal_t(); return mValidateSignal->connect(cb); } + +boost::signals2::connection LLUICtrl::setMouseEnterCallback( const commit_signal_t::slot_type& cb ) +{ + if (!mMouseEnterSignal) mMouseEnterSignal = new commit_signal_t(); + return mMouseEnterSignal->connect(cb); +} + +boost::signals2::connection LLUICtrl::setMouseLeaveCallback( const commit_signal_t::slot_type& cb ) +{ + if (!mMouseLeaveSignal) mMouseLeaveSignal = new commit_signal_t(); + return mMouseLeaveSignal->connect(cb); +} \ No newline at end of file diff --git a/indra/llui/lluictrl.h b/indra/llui/lluictrl.h index 87100c452..6109214cd 100644 --- a/indra/llui/lluictrl.h +++ b/indra/llui/lluictrl.h @@ -70,6 +70,8 @@ public: /*virtual*/ LLXMLNodePtr getXML(bool save_children = true) const; /*virtual*/ BOOL setLabelArg( const std::string& key, const LLStringExplicit& text ); /*virtual*/ BOOL isCtrl() const; + /*virtual*/ void onMouseEnter(S32 x, S32 y, MASK mask); + /*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask); // From LLFocusableElement /*virtual*/ void setFocus( BOOL b ); @@ -127,7 +129,9 @@ public: //Start using these! boost::signals2::connection setCommitCallback( const commit_signal_t::slot_type& cb ); boost::signals2::connection setValidateCallback( const enable_signal_t::slot_type& cb ); - + + boost::signals2::connection setMouseEnterCallback( const commit_signal_t::slot_type& cb ); + boost::signals2::connection setMouseLeaveCallback( const commit_signal_t::slot_type& cb ); // *TODO: Deprecate; for backwards compatability only: //Keeping userdata around with legacy setCommitCallback because it's used ALL OVER THE PLACE. void* getCallbackUserData() const { return mCallbackUserData; } @@ -162,6 +166,9 @@ protected: commit_signal_t* mCommitSignal; enable_signal_t* mValidateSignal; + + commit_signal_t* mMouseEnterSignal; + commit_signal_t* mMouseLeaveSignal; LLViewModelPtr mViewModel; void (*mCommitCallback)( LLUICtrl* ctrl, void* userdata ); diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp index 7b6202a79..9f7cfa092 100644 --- a/indra/llui/llview.cpp +++ b/indra/llui/llview.cpp @@ -677,6 +677,16 @@ BOOL LLView::handleHover(S32 x, S32 y, MASK mask) } +void LLView::onMouseEnter(S32 x, S32 y, MASK mask) +{ + //llinfos << "Mouse entered " << getName() << llendl; +} + +void LLView::onMouseLeave(S32 x, S32 y, MASK mask) +{ + //llinfos << "Mouse left " << getName() << llendl; +} + std::string LLView::getShowNamesToolTip() { LLView* view = getParent(); @@ -2481,6 +2491,56 @@ void LLView::parseFollowsFlags(const LLView::Params& params) } } +LLView::tree_iterator_t LLView::beginTreeDFS() +{ + return tree_iterator_t(this, + boost::bind(boost::mem_fn(&LLView::beginChild), _1), + boost::bind(boost::mem_fn(&LLView::endChild), _1)); +} + +LLView::tree_iterator_t LLView::endTreeDFS() +{ + // an empty iterator is an "end" iterator + return tree_iterator_t(); +} + +LLView::tree_post_iterator_t LLView::beginTreeDFSPost() +{ + return tree_post_iterator_t(this, + boost::bind(boost::mem_fn(&LLView::beginChild), _1), + boost::bind(boost::mem_fn(&LLView::endChild), _1)); +} + +LLView::tree_post_iterator_t LLView::endTreeDFSPost() +{ + // an empty iterator is an "end" iterator + return tree_post_iterator_t(); +} + +LLView::bfs_tree_iterator_t LLView::beginTreeBFS() +{ + return bfs_tree_iterator_t(this, + boost::bind(boost::mem_fn(&LLView::beginChild), _1), + boost::bind(boost::mem_fn(&LLView::endChild), _1)); +} + +LLView::bfs_tree_iterator_t LLView::endTreeBFS() +{ + // an empty iterator is an "end" iterator + return bfs_tree_iterator_t(); +} + + +LLView::root_to_view_iterator_t LLView::beginRootToView() +{ + return root_to_view_iterator_t(this, boost::bind(&LLView::getParent, _1)); +} + +LLView::root_to_view_iterator_t LLView::endRootToView() +{ + return root_to_view_iterator_t(); +} + // static U32 LLView::createRect(LLXMLNodePtr node, LLRect &rect, LLView* parent_view, const LLRect &required_rect) { @@ -2944,6 +3004,16 @@ S32 LLView::notifyParent(const LLSD& info) return parent->notifyParent(info); return 0; } +bool LLView::notifyChildren(const LLSD& info) +{ + bool ret = false; + BOOST_FOREACH(LLView* childp, mChildList) + { + ret = ret || childp->notifyChildren(info); + } + return ret; +} + LLView* LLView::createWidget(LLXMLNodePtr xml_node) const { // forward requests to ui ctrl factory diff --git a/indra/llui/llview.h b/indra/llui/llview.h index 54c298824..9bc969ea6 100644 --- a/indra/llui/llview.h +++ b/indra/llui/llview.h @@ -55,6 +55,7 @@ #include "lluistring.h" #include "llcursortypes.h" #include "llinitparam.h" +#include "lltreeiterators.h" #include "llfocusmgr.h" #include #include "ailist.h" @@ -378,7 +379,24 @@ public: BOOL hasAncestor(const LLView* parentp) const; BOOL hasChild(const std::string& childname, BOOL recurse = FALSE) const; BOOL childHasKeyboardFocus( const std::string& childname ) const; + + // these iterators are used for collapsing various tree traversals into for loops + typedef LLTreeDFSIter tree_iterator_t; + tree_iterator_t beginTreeDFS(); + tree_iterator_t endTreeDFS(); + typedef LLTreeDFSPostIter tree_post_iterator_t; + tree_post_iterator_t beginTreeDFSPost(); + tree_post_iterator_t endTreeDFSPost(); + + typedef LLTreeBFSIter bfs_tree_iterator_t; + bfs_tree_iterator_t beginTreeBFS(); + bfs_tree_iterator_t endTreeBFS(); + + + typedef LLTreeDownIter root_to_view_iterator_t; + root_to_view_iterator_t beginRootToView(); + root_to_view_iterator_t endRootToView(); // // UTILITIES @@ -489,6 +507,9 @@ public: virtual LLView* childFromPoint(S32 x, S32 y, bool recur=false); + // view-specific handlers + virtual void onMouseEnter(S32 x, S32 y, MASK mask); + virtual void onMouseLeave(S32 x, S32 y, MASK mask); template T* findChild(const std::string& name) { return getChild(name,true,false); @@ -607,6 +628,13 @@ public: //send custom notification to LLView parent virtual S32 notifyParent(const LLSD& info); + + //send custom notification to all view childrend + // return true if _any_ children return true. otherwise false. + virtual bool notifyChildren(const LLSD& info); + + //send custom notification to current view + virtual S32 notify(const LLSD& info) { return 0;}; protected: void drawDebugRect(); void drawChild(LLView* childp, S32 x_offset = 0, S32 y_offset = 0, BOOL force_draw = FALSE); diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 5615ce8c5..d354e344c 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -3074,8 +3074,134 @@ void LLViewerWindow::updateUI() BOOL handled = FALSE; LLUICtrl* top_ctrl = gFocusMgr.getTopCtrl(); - LLMouseHandler* mouse_captor = gFocusMgr.getMouseCapture(); + LLView* captor_view = dynamic_cast(mouse_captor); + + //FIXME: only include captor and captor's ancestors if mouse is truly over them --RN + + //build set of views containing mouse cursor by traversing UI hierarchy and testing + //screen rect against mouse cursor + view_handle_set_t mouse_hover_set; + + // constraint mouse enter events to children of mouse captor + LLView* root_view = captor_view; + + // if mouse captor doesn't exist or isn't a LLView + // then allow mouse enter events on entire UI hierarchy + if (!root_view) + { + root_view = mRootView; + } + + // only update mouse hover set when UI is visible (since we shouldn't send hover events to invisible UI +// if (gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI)) + { + // include all ancestors of captor_view as automatically having mouse + if (captor_view) + { + LLView* captor_parent_view = captor_view->getParent(); + while(captor_parent_view) + { + mouse_hover_set.insert(captor_parent_view->getHandle()); + captor_parent_view = captor_parent_view->getParent(); + } + } + + // while the top_ctrl contains the mouse cursor, only it and its descendants will receive onMouseEnter events + if (top_ctrl && top_ctrl->calcScreenBoundingRect().pointInRect(x, y)) + { + // iterator over contents of top_ctrl, and throw into mouse_hover_set + for (LLView::tree_iterator_t it = top_ctrl->beginTreeDFS(); + it != top_ctrl->endTreeDFS(); + ++it) + { + LLView* viewp = *it; + if (viewp->getVisible() + && viewp->calcScreenBoundingRect().pointInRect(x, y)) + { + // we have a view that contains the mouse, add it to the set + mouse_hover_set.insert(viewp->getHandle()); + } + else + { + // skip this view and all of its children + it.skipDescendants(); + } + } + } + else + { + // walk UI tree in depth-first order + for (LLView::tree_iterator_t it = root_view->beginTreeDFS(); + it != root_view->endTreeDFS(); + ++it) + { + LLView* viewp = *it; + // calculating the screen rect involves traversing the parent, so this is less than optimal + if (viewp->getVisible() + && viewp->calcScreenBoundingRect().pointInRect(x, y)) + { + + // if this view is mouse opaque, nothing behind it should be in mouse_hover_set + if (viewp->getMouseOpaque()) + { + // constrain further iteration to children of this widget + it = viewp->beginTreeDFS(); + } + + // we have a view that contains the mouse, add it to the set + mouse_hover_set.insert(viewp->getHandle()); + } + else + { + // skip this view and all of its children + it.skipDescendants(); + } + } + } + } + + typedef std::vector > view_handle_list_t; + + // call onMouseEnter() on all views which contain the mouse cursor but did not before + view_handle_list_t mouse_enter_views; + std::set_difference(mouse_hover_set.begin(), mouse_hover_set.end(), + mMouseHoverViews.begin(), mMouseHoverViews.end(), + std::back_inserter(mouse_enter_views)); + for (view_handle_list_t::iterator it = mouse_enter_views.begin(); + it != mouse_enter_views.end(); + ++it) + { + LLView* viewp = it->get(); + if (viewp) + { + LLRect view_screen_rect = viewp->calcScreenRect(); + viewp->onMouseEnter(x - view_screen_rect.mLeft, y - view_screen_rect.mBottom, mask); + } + } + + // call onMouseLeave() on all views which no longer contain the mouse cursor + view_handle_list_t mouse_leave_views; + std::set_difference(mMouseHoverViews.begin(), mMouseHoverViews.end(), + mouse_hover_set.begin(), mouse_hover_set.end(), + std::back_inserter(mouse_leave_views)); + for (view_handle_list_t::iterator it = mouse_leave_views.begin(); + it != mouse_leave_views.end(); + ++it) + { + LLView* viewp = it->get(); + if (viewp) + { + LLRect view_screen_rect = viewp->calcScreenRect(); + viewp->onMouseLeave(x - view_screen_rect.mLeft, y - view_screen_rect.mBottom, mask); + } + } + + // store resulting hover set for next frame + swap(mMouseHoverViews, mouse_hover_set); + + // only handle hover events when UI is enabled +// if (gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI)) { if( mouse_captor ) diff --git a/indra/newview/llviewerwindow.h b/indra/newview/llviewerwindow.h index a84413734..5af0ad7b7 100644 --- a/indra/newview/llviewerwindow.h +++ b/indra/newview/llviewerwindow.h @@ -433,6 +433,8 @@ protected: BOOL mMouseInWindow; // True if the mouse is over our window or if we have captured the mouse. BOOL mFocusCycleMode; + typedef std::set > view_handle_set_t; + view_handle_set_t mMouseHoverViews; // Variables used for tool override switching based on modifier keys. JC MASK mLastMask; // used to detect changes in modifier mask