Sfoglia il codice sorgente

Mouse leaving the window is now properly handled by GUIManager and hover states on GUIElements are properly reset

Marko Pintera 12 anni fa
parent
commit
5f4d53a8b7

+ 6 - 0
BansheeEngine/Include/BsGUIManager.h

@@ -110,6 +110,8 @@ namespace BansheeEngine
 		boost::signals::connection mWindowLostFocusConn;
 		boost::signals::connection mWindowMovedOrResizedConn;
 
+		boost::signals::connection mMouseLeftWindowConn;
+
 		void updateMeshes();
 		void updateCaretTexture();
 		void updateTextSelectionTexture();
@@ -127,6 +129,10 @@ namespace BansheeEngine
 		void onWindowFocusLost(CM::RenderWindow& win);
 		void onWindowMovedOrResized(CM::RenderWindow& win);
 
+		void onMouseLeftWindow(CM::RenderWindow* win);
+
+		bool handleMouseOver(GUIWidget* widget, GUIElement* element, const CM::Int2& screenMousePos, float wheelScrollAmount = 0.0f);
+
 		GUIMouseButton buttonToMouseButton(CM::ButtonCode code) const;
 		CM::Int2 getWidgetRelativePos(const GUIWidget& widget, const CM::Int2& screenPos) const;
 

+ 91 - 56
BansheeEngine/Source/BsGUIManager.cpp

@@ -62,6 +62,8 @@ namespace BansheeEngine
 		mWindowLostFocusConn = RenderWindowManager::instance().onFocusLost.connect(boost::bind(&GUIManager::onWindowFocusLost, this, _1));
 		mWindowMovedOrResizedConn = RenderWindowManager::instance().onMovedOrResized.connect(boost::bind(&GUIManager::onWindowMovedOrResized, this, _1));
 
+		mMouseLeftWindowConn = Platform::onMouseLeftWindow.connect(boost::bind(&GUIManager::onMouseLeftWindow, this, _1));
+
 		mInputCaret = cm_new<GUIInputCaret, PoolAlloc>();
 		mInputSelection = cm_new<GUIInputSelection, PoolAlloc>();
 
@@ -94,6 +96,8 @@ namespace BansheeEngine
 		mWindowLostFocusConn.disconnect();
 		mWindowMovedOrResizedConn.disconnect();
 
+		mMouseLeftWindowConn.disconnect();
+
 		cm_delete<PoolAlloc>(mInputCaret);
 		cm_delete<PoolAlloc>(mInputSelection);
 	}
@@ -704,18 +708,6 @@ namespace BansheeEngine
 		}
 #endif
 
-		bool shiftDown = gInput().isButtonDown(BC_LSHIFT) || gInput().isButtonDown(BC_RSHIFT);
-		bool ctrlDown = gInput().isButtonDown(BC_LCONTROL) || gInput().isButtonDown(BC_RCONTROL);
-		bool altDown = gInput().isButtonDown(BC_LMENU) || gInput().isButtonDown(BC_RMENU);
-
-		// TODO - Maybe avoid querying these for every event separately?
-		bool buttonStates[(int)GUIMouseButton::Count];
-		buttonStates[0] = gInput().isButtonDown(BC_MOUSE_LEFT);
-		buttonStates[1] = gInput().isButtonDown(BC_MOUSE_MIDDLE);
-		buttonStates[2] = gInput().isButtonDown(BC_MOUSE_RIGHT);
-
-		mMouseEvent = GUIMouseEvent(buttonStates, shiftDown, ctrlDown, altDown);
-
 		GUIWidget* widgetInFocus = nullptr;
 		GUIElement* topMostElement = nullptr;
 		Int2 screenPos;
@@ -775,31 +767,84 @@ namespace BansheeEngine
 			}
 		}
 
+		if(handleMouseOver(widgetInFocus, topMostElement, event.screenPos, event.mouseWheelScrollAmount))
+			event.markAsUsed();
+	}
+
+	void GUIManager::onTextInput(const CM::TextInputEvent& event)
+	{
+		if(mKeyboardFocusElement != nullptr)
+		{
+			bool shiftDown = gInput().isButtonDown(BC_LSHIFT) || gInput().isButtonDown(BC_RSHIFT);
+			bool ctrlDown = gInput().isButtonDown(BC_LCONTROL) || gInput().isButtonDown(BC_RCONTROL);
+			bool altDown = gInput().isButtonDown(BC_LMENU) || gInput().isButtonDown(BC_RMENU);
+
+			if(ctrlDown || altDown) // Ignore text input because key characters + alt/ctrl usually correspond to certain commands
+				return;
+
+			mKeyEvent = GUIKeyEvent(shiftDown, ctrlDown, altDown);
+
+			mKeyEvent.setTextInputData(event.textChar);
+			if(sendKeyEvent(mKeyboardFocusWidget, mKeyboardFocusElement, mKeyEvent))
+				event.markAsUsed();
+		}
+	}
+
+	bool GUIManager::handleMouseOver(GUIWidget* widget, GUIElement* element, const CM::Int2& screenMousePos, float wheelScrollAmount)
+	{
+		bool eventProcessed = false;
+
+		Int2 localPos;
+		if(widget != nullptr)
+		{
+			const RenderWindow* window = widget->getOwnerWindow();
+
+			Int2 screenPos = window->screenToWindowPos(screenMousePos);
+			Vector4 vecScreenPos((float)screenPos.x, (float)screenPos.y, 0.0f, 1.0f);
+
+			const Matrix4& worldTfrm = widget->SO()->getWorldTfrm();
+
+			Vector4 vecLocalPos = worldTfrm.inverse() * vecScreenPos;
+			localPos = Int2(Math::RoundToInt(vecLocalPos.x), Math::RoundToInt(vecLocalPos.y));
+		}
+
+		bool shiftDown = gInput().isButtonDown(BC_LSHIFT) || gInput().isButtonDown(BC_RSHIFT);
+		bool ctrlDown = gInput().isButtonDown(BC_LCONTROL) || gInput().isButtonDown(BC_RCONTROL);
+		bool altDown = gInput().isButtonDown(BC_LMENU) || gInput().isButtonDown(BC_RMENU);
+
+		// TODO - Maybe avoid querying these for every event separately?
+		bool buttonStates[(int)GUIMouseButton::Count];
+		buttonStates[0] = gInput().isButtonDown(BC_MOUSE_LEFT);
+		buttonStates[1] = gInput().isButtonDown(BC_MOUSE_MIDDLE);
+		buttonStates[2] = gInput().isButtonDown(BC_MOUSE_RIGHT);
+
+		mMouseEvent = GUIMouseEvent(buttonStates, shiftDown, ctrlDown, altDown);
+
 		// Send MouseOver/MouseOut events to any elements the mouse passes over, except when
 		// mouse is being held down, in which we only send them to the active element
-		if(topMostElement != mMouseOverElement)
+		if(element != mMouseOverElement)
 		{
 			if(mMouseOverElement != nullptr)
 			{
 				// Send MouseOut event
 				if(mActiveElement == nullptr || mMouseOverElement == mActiveElement)
 				{
-					Int2 curLocalPos = getWidgetRelativePos(*mMouseOverWidget, event.screenPos);
+					Int2 curLocalPos = getWidgetRelativePos(*mMouseOverWidget, screenMousePos);
 
-					mMouseEvent.setMouseOutData(topMostElement, curLocalPos);
+					mMouseEvent.setMouseOutData(element, curLocalPos);
 					if(sendMouseEvent(mMouseOverWidget, mMouseOverElement, mMouseEvent))
-						event.markAsUsed();
+						eventProcessed = true;
 				}
 			}
 
-			if(topMostElement != nullptr)
+			if(element != nullptr)
 			{
 				// Send MouseOver event
-				if(mActiveElement == nullptr || topMostElement == mActiveElement)
+				if(mActiveElement == nullptr || element == mActiveElement)
 				{
-					mMouseEvent.setMouseOverData(topMostElement, localPos);
-					if(sendMouseEvent(widgetInFocus, topMostElement, mMouseEvent))
-						event.markAsUsed();
+					mMouseEvent.setMouseOverData(element, localPos);
+					if(sendMouseEvent(widget, element, mMouseEvent))
+						eventProcessed = true;
 				}
 			}
 		}
@@ -807,13 +852,13 @@ namespace BansheeEngine
 		// If mouse is being held down send MouseDrag events
 		if(mActiveElement != nullptr)
 		{
-			Int2 curLocalPos = getWidgetRelativePos(*mActiveWidget, event.screenPos);
+			Int2 curLocalPos = getWidgetRelativePos(*mActiveWidget, screenMousePos);
 
 			if(mLastCursorLocalPos != curLocalPos)
 			{
-				mMouseEvent.setMouseDragData(topMostElement, curLocalPos, curLocalPos - mLastCursorLocalPos);
+				mMouseEvent.setMouseDragData(element, curLocalPos, curLocalPos - mLastCursorLocalPos);
 				if(sendMouseEvent(mActiveWidget, mActiveElement, mMouseEvent))
-					event.markAsUsed();
+					eventProcessed = true;
 
 				mLastCursorLocalPos = curLocalPos;
 			}
@@ -821,58 +866,41 @@ namespace BansheeEngine
 			// Also if drag is in progress send DragAndDrop events
 			if(DragAndDropManager::instance().isDragInProgress())
 			{
-				if(topMostElement != nullptr)
+				if(element != nullptr)
 				{
-					mMouseEvent.setDragAndDropDraggedData(topMostElement, localPos, DragAndDropManager::instance().getDragTypeId(), DragAndDropManager::instance().getDragData());
-					if(sendMouseEvent(widgetInFocus, topMostElement, mMouseEvent))
-						event.markAsUsed();
+					mMouseEvent.setDragAndDropDraggedData(element, localPos, DragAndDropManager::instance().getDragTypeId(), DragAndDropManager::instance().getDragData());
+					if(sendMouseEvent(widget, element, mMouseEvent))
+						eventProcessed = true;
 				}
 			}
 		}
 		else // Otherwise, send MouseMove events if we are hovering over any element
 		{
-			if(topMostElement != nullptr)
+			if(element != nullptr)
 			{
 				// Send MouseMove event
 				if(mLastCursorLocalPos != localPos)
 				{
-					mMouseEvent.setMouseMoveData(topMostElement, localPos);
-					if(sendMouseEvent(widgetInFocus, topMostElement, mMouseEvent))
-						event.markAsUsed();
+					mMouseEvent.setMouseMoveData(element, localPos);
+					if(sendMouseEvent(widget, element, mMouseEvent))
+						eventProcessed = true;
 
 					mLastCursorLocalPos = localPos;
 				}
 
-				if(Math::Abs(event.mouseWheelScrollAmount) > 0.00001f)
+				if(Math::Abs(wheelScrollAmount) > 0.00001f)
 				{
-					mMouseEvent.setMouseWheelScrollData(topMostElement, event.mouseWheelScrollAmount);
-					if(sendMouseEvent(widgetInFocus, topMostElement, mMouseEvent))
-						event.markAsUsed();
+					mMouseEvent.setMouseWheelScrollData(element, wheelScrollAmount);
+					if(sendMouseEvent(widget, element, mMouseEvent))
+						eventProcessed = true;
 				}
 			}
 		}
 
-		mMouseOverElement = topMostElement;
-		mMouseOverWidget = widgetInFocus;
-	}
-
-	void GUIManager::onTextInput(const CM::TextInputEvent& event)
-	{
-		if(mKeyboardFocusElement != nullptr)
-		{
-			bool shiftDown = gInput().isButtonDown(BC_LSHIFT) || gInput().isButtonDown(BC_RSHIFT);
-			bool ctrlDown = gInput().isButtonDown(BC_LCONTROL) || gInput().isButtonDown(BC_RCONTROL);
-			bool altDown = gInput().isButtonDown(BC_LMENU) || gInput().isButtonDown(BC_RMENU);
-
-			if(ctrlDown || altDown) // Ignore text input because key characters + alt/ctrl usually correspond to certain commands
-				return;
+		mMouseOverElement = element;
+		mMouseOverWidget = widget;
 
-			mKeyEvent = GUIKeyEvent(shiftDown, ctrlDown, altDown);
-
-			mKeyEvent.setTextInputData(event.textChar);
-			if(sendKeyEvent(mKeyboardFocusWidget, mKeyboardFocusElement, mKeyEvent))
-				event.markAsUsed();
-		}
+		return false;
 	}
 
 	void GUIManager::onWindowFocusGained(RenderWindow& win)
@@ -905,6 +933,13 @@ namespace BansheeEngine
 		}
 	}
 
+	// We stop getting mouse move events once it leaves the window, so make sure
+	// nothing stays in hover state
+	void GUIManager::onMouseLeftWindow(CM::RenderWindow* win)
+	{
+		handleMouseOver(nullptr, nullptr, Int2());
+	}
+
 	void GUIManager::queueForDestroy(GUIElement* element)
 	{
 		mScheduledForDestruction.push(element);

+ 14 - 0
CamelotCore/Include/Win32/CmPlatformImpl.h

@@ -161,9 +161,20 @@ namespace CamelotFramework
 		 * @brief	Message pump. Processes OS messages and returns when it's free.
 		 * 			
 		 * @note	This method must be called from the core thread.
+		 * 			Internal method.
 		 */
 		static void messagePump();
 
+		/**
+		 * @brief	Called once per frame from the sim thread.
+		 * 			
+		 * @note	Internal method.
+		 */
+		static void update();
+
+		// Callbacks triggered on the sim thread
+		static boost::signal<void(RenderWindow*)> onMouseLeftWindow;
+
 		// Callbacks triggered on the core thread. Be careful so that none
 		// of the connected methods call methods intended for sim thread.
 		static boost::signal<void(const Int2&)> onMouseMoved;
@@ -179,6 +190,9 @@ namespace CamelotFramework
 		static bool mUsingCustomCursor;
 		static Map<const RenderWindow*, WindowNonClientAreaData>::type mNonClientAreas;
 
+		static bool mIsTrackingMouse;
+		static Vector<RenderWindow*>::type mMouseLeftWindows;
+
 		CM_STATIC_MUTEX(mSync);
 
 		static void win32ShowCursor();

+ 1 - 0
CamelotCore/Source/CmApplication.cpp

@@ -91,6 +91,7 @@ namespace CamelotFramework
 
 		while(mRunMainLoop)
 		{
+			Platform::update();
 			DeferredCallManager::instance().update();
 			RenderWindowManager::instance().update();
 			gInput().update();

+ 19 - 0
CamelotCore/Source/CmPlatformWndProc.cpp

@@ -157,9 +157,28 @@ namespace CamelotFramework
 
 				return HTCLIENT;
 			}
+		case WM_MOUSELEAVE:
+			{
+				CM_LOCK_MUTEX(mSync);
+
+				mMouseLeftWindows.push_back(win);
+				mIsTrackingMouse = false; // TrackMouseEvent ends when this message is received and needs to be re-applied
+			}
+			break;
 		case WM_NCMOUSEMOVE:
 		case WM_MOUSEMOVE:
 			{
+				// Set up tracking so we get notified when mouse leaves the window
+				if(!mIsTrackingMouse)
+				{
+					TRACKMOUSEEVENT tme = { sizeof(tme) };
+					tme.dwFlags = TME_LEAVE;
+					tme.hwndTrack = hWnd;
+					TrackMouseEvent(&tme);
+
+					mIsTrackingMouse = true;
+				}
+
 				POINT mousePos;
 
 				mousePos.x = GET_X_LPARAM(lParam);

+ 21 - 0
CamelotCore/Source/Win32/CmPlatformImpl.cpp

@@ -6,6 +6,8 @@
 
 namespace CamelotFramework
 {
+	boost::signal<void(RenderWindow*)> Platform::onMouseLeftWindow;
+
 	boost::signal<void(const Int2&)> Platform::onMouseMoved;
 	boost::signal<void(float)> Platform::onMouseWheelScrolled;
 	boost::signal<void(UINT32)> Platform::onCharInput;
@@ -16,6 +18,8 @@ namespace CamelotFramework
 	boost::signal<void()> Platform::onMouseCaptureChanged;
 
 	Map<const RenderWindow*, WindowNonClientAreaData>::type Platform::mNonClientAreas;
+	bool Platform::mIsTrackingMouse = false;
+	Vector<RenderWindow*>::type Platform::mMouseLeftWindows;
 
 	CM_STATIC_MUTEX_CLASS_INSTANCE(mSync, Platform);
 
@@ -281,6 +285,23 @@ namespace CamelotFramework
 		}
 	}
 
+	void Platform::update()
+	{
+		Vector<RenderWindow*>::type windowsCopy;
+		{
+			CM_LOCK_MUTEX(mSync);
+
+			windowsCopy = mMouseLeftWindows;
+			mMouseLeftWindows.clear();
+		}
+		
+		for(auto& window : windowsCopy)
+		{
+			if(!onMouseLeftWindow.empty())
+				onMouseLeftWindow(window);
+		}
+	}
+
 	void Platform::windowFocusReceived(RenderWindow* window)
 	{
 		if(!onWindowFocusReceived.empty())

+ 9 - 1
EditorWindowDock.txt

@@ -4,12 +4,20 @@ Add icons to drag and drop - There's a built-in windows icon for this. I think.
 Add highlight to WindowMover so I can know when I'm mousing over it with a dragged window in hand
 Ensure that dropping a window onto a mover will actually docks it properly
 
-Moving a cursor to a non-client area doesn't seem to refresh the GUIElements properly. They are handled as if I was mousing over them.
+GUIManager only changes mouse over events in MouseMove method, but that method doesn't get called when mouse leaves the window!
+
 I still have the issue where GUIManager hack code got triggered
 Get rid of the GUIManager mouseUp hack when I ensure resize/move using non client areas work
 
 Prevent docking if available size is less than 20 pixels, otherwise there might be some weirdness
 
+Possible solution to MouseUp problem:
+  on WM_ENTERSIZEMOVE call OIS and SetCooperativeLevel(BACKGROUND)
+  and on WM_EXITSIZEMOVE revert back to FOREGROUND coop level
+
+And for MouseMove problem:
+  Track mouse leave events, add a handler to Platform and hook up GUIManager to it
+
 ------------------------
 
 Other things to remember: