Explorar o código

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

Marko Pintera %!s(int64=12) %!d(string=hai) anos
pai
achega
5f4d53a8b7

+ 6 - 0
BansheeEngine/Include/BsGUIManager.h

@@ -110,6 +110,8 @@ namespace BansheeEngine
 		boost::signals::connection mWindowLostFocusConn;
 		boost::signals::connection mWindowLostFocusConn;
 		boost::signals::connection mWindowMovedOrResizedConn;
 		boost::signals::connection mWindowMovedOrResizedConn;
 
 
+		boost::signals::connection mMouseLeftWindowConn;
+
 		void updateMeshes();
 		void updateMeshes();
 		void updateCaretTexture();
 		void updateCaretTexture();
 		void updateTextSelectionTexture();
 		void updateTextSelectionTexture();
@@ -127,6 +129,10 @@ namespace BansheeEngine
 		void onWindowFocusLost(CM::RenderWindow& win);
 		void onWindowFocusLost(CM::RenderWindow& win);
 		void onWindowMovedOrResized(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;
 		GUIMouseButton buttonToMouseButton(CM::ButtonCode code) const;
 		CM::Int2 getWidgetRelativePos(const GUIWidget& widget, const CM::Int2& screenPos) 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));
 		mWindowLostFocusConn = RenderWindowManager::instance().onFocusLost.connect(boost::bind(&GUIManager::onWindowFocusLost, this, _1));
 		mWindowMovedOrResizedConn = RenderWindowManager::instance().onMovedOrResized.connect(boost::bind(&GUIManager::onWindowMovedOrResized, 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>();
 		mInputCaret = cm_new<GUIInputCaret, PoolAlloc>();
 		mInputSelection = cm_new<GUIInputSelection, PoolAlloc>();
 		mInputSelection = cm_new<GUIInputSelection, PoolAlloc>();
 
 
@@ -94,6 +96,8 @@ namespace BansheeEngine
 		mWindowLostFocusConn.disconnect();
 		mWindowLostFocusConn.disconnect();
 		mWindowMovedOrResizedConn.disconnect();
 		mWindowMovedOrResizedConn.disconnect();
 
 
+		mMouseLeftWindowConn.disconnect();
+
 		cm_delete<PoolAlloc>(mInputCaret);
 		cm_delete<PoolAlloc>(mInputCaret);
 		cm_delete<PoolAlloc>(mInputSelection);
 		cm_delete<PoolAlloc>(mInputSelection);
 	}
 	}
@@ -704,18 +708,6 @@ namespace BansheeEngine
 		}
 		}
 #endif
 #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;
 		GUIWidget* widgetInFocus = nullptr;
 		GUIElement* topMostElement = nullptr;
 		GUIElement* topMostElement = nullptr;
 		Int2 screenPos;
 		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
 		// 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
 		// mouse is being held down, in which we only send them to the active element
-		if(topMostElement != mMouseOverElement)
+		if(element != mMouseOverElement)
 		{
 		{
 			if(mMouseOverElement != nullptr)
 			if(mMouseOverElement != nullptr)
 			{
 			{
 				// Send MouseOut event
 				// Send MouseOut event
 				if(mActiveElement == nullptr || mMouseOverElement == mActiveElement)
 				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))
 					if(sendMouseEvent(mMouseOverWidget, mMouseOverElement, mMouseEvent))
-						event.markAsUsed();
+						eventProcessed = true;
 				}
 				}
 			}
 			}
 
 
-			if(topMostElement != nullptr)
+			if(element != nullptr)
 			{
 			{
 				// Send MouseOver event
 				// 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 mouse is being held down send MouseDrag events
 		if(mActiveElement != nullptr)
 		if(mActiveElement != nullptr)
 		{
 		{
-			Int2 curLocalPos = getWidgetRelativePos(*mActiveWidget, event.screenPos);
+			Int2 curLocalPos = getWidgetRelativePos(*mActiveWidget, screenMousePos);
 
 
 			if(mLastCursorLocalPos != curLocalPos)
 			if(mLastCursorLocalPos != curLocalPos)
 			{
 			{
-				mMouseEvent.setMouseDragData(topMostElement, curLocalPos, curLocalPos - mLastCursorLocalPos);
+				mMouseEvent.setMouseDragData(element, curLocalPos, curLocalPos - mLastCursorLocalPos);
 				if(sendMouseEvent(mActiveWidget, mActiveElement, mMouseEvent))
 				if(sendMouseEvent(mActiveWidget, mActiveElement, mMouseEvent))
-					event.markAsUsed();
+					eventProcessed = true;
 
 
 				mLastCursorLocalPos = curLocalPos;
 				mLastCursorLocalPos = curLocalPos;
 			}
 			}
@@ -821,58 +866,41 @@ namespace BansheeEngine
 			// Also if drag is in progress send DragAndDrop events
 			// Also if drag is in progress send DragAndDrop events
 			if(DragAndDropManager::instance().isDragInProgress())
 			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
 		else // Otherwise, send MouseMove events if we are hovering over any element
 		{
 		{
-			if(topMostElement != nullptr)
+			if(element != nullptr)
 			{
 			{
 				// Send MouseMove event
 				// Send MouseMove event
 				if(mLastCursorLocalPos != localPos)
 				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;
 					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)
 	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)
 	void GUIManager::queueForDestroy(GUIElement* element)
 	{
 	{
 		mScheduledForDestruction.push(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.
 		 * @brief	Message pump. Processes OS messages and returns when it's free.
 		 * 			
 		 * 			
 		 * @note	This method must be called from the core thread.
 		 * @note	This method must be called from the core thread.
+		 * 			Internal method.
 		 */
 		 */
 		static void messagePump();
 		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
 		// Callbacks triggered on the core thread. Be careful so that none
 		// of the connected methods call methods intended for sim thread.
 		// of the connected methods call methods intended for sim thread.
 		static boost::signal<void(const Int2&)> onMouseMoved;
 		static boost::signal<void(const Int2&)> onMouseMoved;
@@ -179,6 +190,9 @@ namespace CamelotFramework
 		static bool mUsingCustomCursor;
 		static bool mUsingCustomCursor;
 		static Map<const RenderWindow*, WindowNonClientAreaData>::type mNonClientAreas;
 		static Map<const RenderWindow*, WindowNonClientAreaData>::type mNonClientAreas;
 
 
+		static bool mIsTrackingMouse;
+		static Vector<RenderWindow*>::type mMouseLeftWindows;
+
 		CM_STATIC_MUTEX(mSync);
 		CM_STATIC_MUTEX(mSync);
 
 
 		static void win32ShowCursor();
 		static void win32ShowCursor();

+ 1 - 0
CamelotCore/Source/CmApplication.cpp

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

+ 19 - 0
CamelotCore/Source/CmPlatformWndProc.cpp

@@ -157,9 +157,28 @@ namespace CamelotFramework
 
 
 				return HTCLIENT;
 				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_NCMOUSEMOVE:
 		case WM_MOUSEMOVE:
 		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;
 				POINT mousePos;
 
 
 				mousePos.x = GET_X_LPARAM(lParam);
 				mousePos.x = GET_X_LPARAM(lParam);

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

@@ -6,6 +6,8 @@
 
 
 namespace CamelotFramework
 namespace CamelotFramework
 {
 {
+	boost::signal<void(RenderWindow*)> Platform::onMouseLeftWindow;
+
 	boost::signal<void(const Int2&)> Platform::onMouseMoved;
 	boost::signal<void(const Int2&)> Platform::onMouseMoved;
 	boost::signal<void(float)> Platform::onMouseWheelScrolled;
 	boost::signal<void(float)> Platform::onMouseWheelScrolled;
 	boost::signal<void(UINT32)> Platform::onCharInput;
 	boost::signal<void(UINT32)> Platform::onCharInput;
@@ -16,6 +18,8 @@ namespace CamelotFramework
 	boost::signal<void()> Platform::onMouseCaptureChanged;
 	boost::signal<void()> Platform::onMouseCaptureChanged;
 
 
 	Map<const RenderWindow*, WindowNonClientAreaData>::type Platform::mNonClientAreas;
 	Map<const RenderWindow*, WindowNonClientAreaData>::type Platform::mNonClientAreas;
+	bool Platform::mIsTrackingMouse = false;
+	Vector<RenderWindow*>::type Platform::mMouseLeftWindows;
 
 
 	CM_STATIC_MUTEX_CLASS_INSTANCE(mSync, Platform);
 	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)
 	void Platform::windowFocusReceived(RenderWindow* window)
 	{
 	{
 		if(!onWindowFocusReceived.empty())
 		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
 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
 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
 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
 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
 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:
 Other things to remember: