2
0
Эх сурвалжийг харах

More work on mouse events

Marko Pintera 12 жил өмнө
parent
commit
588d28952d

+ 7 - 0
BansheeEngine/Include/BsApplication.h

@@ -2,6 +2,8 @@
 
 #include "BsPrerequisites.h"
 
+#include "boost/signals/connection.hpp"
+
 namespace BansheeEngine
 {
 	class BS_EXPORT Application
@@ -25,6 +27,11 @@ namespace BansheeEngine
 			 * @brief	Frees up all resources allocated during startUp, and while the application was running.
 			 */
 			void shutDown();
+
+	private:
+		boost::signals::connection updateCallbackConn;
+
+		void update();
 	};
 
 	BS_EXPORT Application& gBansheeApp();

+ 2 - 0
BansheeEngine/Include/BsGUIElement.h

@@ -64,6 +64,8 @@ namespace BansheeEngine
 		virtual void fillBuffer(UINT8* vertices, UINT8* uv, UINT32* indices, UINT32 startingQuad, 
 			UINT32 maxNumQuads, UINT32 vertexStride, UINT32 indexStride, UINT32 renderElementIdx) const = 0;
 
+		virtual bool mouseEvent(const GUIMouseEvent& ev);
+
 		const CM::Rect& getBounds() const { return mBounds; }
 
 		void setDepth(INT32 depth) { mDepth = depth; }

+ 2 - 0
BansheeEngine/Include/BsGUIManager.h

@@ -23,6 +23,8 @@ namespace BansheeEngine
 		 */
 		void attachWidgetToWindow(const CM::RenderWindow* window, GUIWidget* widget);
 
+		void detachWidgetFromWindow(const CM::RenderWindow* window, GUIWidget* widget);
+
 		void update();
 	private:
 		std::vector<GUIWidget*> mWidgets;

+ 4 - 3
BansheeEngine/Include/BsGUIMouseEvent.h

@@ -1,18 +1,19 @@
 #pragma once
 
 #include "BsPrerequisites.h"
-#include "CmVector2.h"
+#include "CmInt2.h"
 
 namespace BansheeEngine
 {
 	class BS_EXPORT GUIMouseEvent
 	{
 	public:
-		GUIMouseEvent(const CM::Vector2& position)
+		GUIMouseEvent(const CM::Int2& position)
 			:mPosition(position)
 		{ }
 
+		const CM::Int2& getPosition() const { return mPosition; }
 	private:
-		CM::Vector2 mPosition;
+		CM::Int2 mPosition;
 	};
 }

+ 4 - 3
BansheeEngine/Include/BsGUIWidget.h

@@ -16,6 +16,9 @@ namespace BansheeEngine
 		void setSkin(const GUISkin* skin);
 		const GUISkin* getGUISkin() const;
 
+		bool inBounds(const CM::Int2& position) const;
+		bool mouseEvent(const GUIMouseEvent& ev);
+
 		virtual void render(const Camera* camera, CM::RenderContext& renderContext) const;
 	protected:
 		friend class CM::SceneObject;
@@ -23,8 +26,6 @@ namespace BansheeEngine
 
 		GUIWidget(const CM::HSceneObject& parent);
 
-		void mouseEvent(const GUIMouseEvent& ev);
-
 		void registerElement(GUIElement* elem);
 	private:
 
@@ -33,7 +34,7 @@ namespace BansheeEngine
 
 		std::vector<GUIElement*> mElements;
 		
-		mutable CM::Rect mBounds;
+		mutable CM::ORect mBounds;
 		mutable std::vector<std::pair<CM::ORect, GUIElement*>> mCachedBounds;
 		mutable std::vector<CM::HMesh> mCachedMeshes;
 		mutable std::vector<CM::HMaterial> mCachedMaterials;

+ 1 - 1
BansheeEngine/Include/BsPrerequisites.h

@@ -48,7 +48,7 @@ namespace BansheeEngine
 	class GUILabel;
 	class GUISkin;
 	struct GUIElementStyle;
-	struct GUIMouseEvent;
+	class GUIMouseEvent;
 
 	// 2D
 	class TextSprite;

+ 9 - 0
BansheeEngine/Source/BsApplication.cpp

@@ -45,6 +45,8 @@ namespace BansheeEngine
 		BuiltinMaterialManager::instance().setActive(desc.renderSystem);
 
 		EngineGUI::startUp(new EngineGUI());
+
+		updateCallbackConn = CM::gApplication().mainLoopCallback.connect(boost::bind(&Application::update, this));
 	}
 
 	void Application::runMainLoop()
@@ -54,6 +56,8 @@ namespace BansheeEngine
 
 	void Application::shutDown()
 	{
+		CM::gApplication().mainLoopCallback.disconnect(updateCallbackConn);
+
 		EngineGUI::shutDown();
 
 		GUIMaterialManager::instance().forceReleaseAllMaterials();
@@ -67,6 +71,11 @@ namespace BansheeEngine
 		GUIManager::shutDown();
 	}
 
+	void Application::update()
+	{
+		GUIManager::instance().update();
+	}
+
 	Application& gBansheeApp()
 	{
 		static Application application;

+ 5 - 0
BansheeEngine/Source/BsGUIElement.cpp

@@ -21,6 +21,11 @@ namespace BansheeEngine
 
 	}
 
+	bool GUIElement::mouseEvent(const GUIMouseEvent& ev)
+	{
+		return false;
+	}
+
 	void GUIElement::destroy(GUIElement* element)
 	{
 		CM_DELETE(element, GUIElement, PoolAlloc);

+ 46 - 5
BansheeEngine/Source/BsGUIManager.cpp

@@ -1,6 +1,7 @@
 #include "BsGUIManager.h"
 #include "BsGUIWidget.h"
 #include "BsGUIElement.h"
+#include "BsGUIMouseEvent.h"
 #include "CmMaterial.h"
 #include "CmMeshData.h"
 #include "CmMesh.h"
@@ -15,14 +16,12 @@ namespace BansheeEngine
 {
 	GUIManager::GUIManager()
 	{
-		//windowCreateConn = RenderWindowManager::instance().onWindowCreated.connect(boost::bind(&GUIManager::doOnWindowCreated, this, _1));
-		//windowDestroyConn = RenderWindowManager::instance().onWindowDestroyed.connect(boost::bind(&GUIManager::doOnWindowDestroyed, this, _1));
+
 	}
 
 	GUIManager::~GUIManager()
 	{
-		//RenderWindowManager::instance().onWindowCreated.disconnect(windowCreateConn);
-		//RenderWindowManager::instance().onWindowDestroyed.disconnect(windowDestroyConn);
+
 	}
 
 	void GUIManager::registerWidget(GUIWidget* widget)
@@ -73,16 +72,58 @@ namespace BansheeEngine
 			widgets.push_back(widget);
 	}
 
+	void GUIManager::detachWidgetFromWindow(const CM::RenderWindow* window, GUIWidget* widget)
+	{
+		auto findIter = mWindowWidgetMap.find(window);
+
+		if(findIter == mWindowWidgetMap.end())
+		{
+			CM_EXCEPT(InternalErrorException, "Cannot find window to detach the widget from.");
+		}
+
+		std::vector<GUIWidget*>& widgets = findIter->second;
+		auto findIter2 = std::find(begin(widgets), end(widgets), widget);
+
+		if(findIter2 == widgets.end())
+		{
+			CM_EXCEPT(InternalErrorException, "Cannot find widget attached to the specified window.");
+		}
+
+		widgets.erase(findIter2);
+	}
+
 	void GUIManager::update()
 	{
+#if CM_DEBUG_MODE
+		// Checks if all referenced windows actually exist
+		std::vector<RenderWindow*> activeWindows = RenderWindowManager::instance().getRenderWindows();
+		for(auto& window : mWindowWidgetMap)
+		{
+			auto iterFind = std::find(begin(activeWindows), end(activeWindows), window.first);
+
+			if(iterFind == activeWindows.end())
+			{
+				CM_EXCEPT(InternalErrorException, "GUI manager has a reference to a window that doesn't exist. \
+												  Please detach all GUIWidgets from windows before destroying a window.");
+			}
+		}
+#endif
+
 		for(auto& window : mWindowWidgetMap)
 		{
 			if(!window.first->getHasFocus())
 				continue;
 
+			Int2 screenPos = Cursor::getWindowPosition(*window.first);
+			GUIMouseEvent mouseEvent(screenPos);
+
 			for(auto& widget : mWidgets)
 			{
-				Int2 screenPos = Cursor::getWindowPosition(*window.first);
+				if(widget->inBounds(screenPos))
+				{
+					if(widget->mouseEvent(mouseEvent))
+						break;
+				}
 			}
 		}
 	}

+ 37 - 4
BansheeEngine/Source/BsGUIWidget.cpp

@@ -2,11 +2,13 @@
 #include "BsGUIManager.h"
 #include "BsGUISkin.h"
 #include "BsGUILabel.h"
+#include "BsGUIMouseEvent.h"
 #include "CmApplication.h"
 #include "CmDeferredRenderContext.h"
 #include "CmMaterial.h"
 #include "CmPass.h"
 #include "CmMesh.h"
+#include "CmInt2.h"
 #include "BsCamera.h"
 #include "CmViewport.h"
 #include "CmSceneObject.h"
@@ -53,9 +55,23 @@ namespace BansheeEngine
 			return &DefaultSkin;
 	}
 
-	void GUIWidget::mouseEvent(const GUIMouseEvent& ev)
+	bool GUIWidget::mouseEvent(const GUIMouseEvent& ev)
 	{
+		Vector2 vecPos((float)ev.getPosition().x, (float)ev.getPosition().y);
 
+		// Elements with lowest depth (most to the front) get handled first
+		for(auto iter = mCachedBounds.rbegin(); iter != mCachedBounds.rend(); ++iter)
+		{
+			ORect& bounds = iter->first;
+
+			if(bounds.contains(vecPos))
+			{
+				if(iter->second->mouseEvent(ev))
+					return true;
+			}
+		}
+
+		return false;
 	}
 
 	void GUIWidget::updateMeshes() const
@@ -176,19 +192,36 @@ namespace BansheeEngine
 		updateBounds();
 	}
 
+	bool GUIWidget::inBounds(const Int2& position) const
+	{
+		Vector2 vecPos((float)position.x, (float)position.y);
+
+		return mBounds.contains(vecPos);
+	}
+
 	void GUIWidget::updateBounds() const
 	{
 		mCachedBounds.clear();
 
 		const Matrix4& worldTfrm = SO()->getWorldTfrm();
 
+		Rect widgetBounds;
+		if(mElements.size() > 0)
+			widgetBounds = mElements[0]->getBounds();
+
 		for(auto& elem : mElements)
 		{
-			ORect elemBounds(elem->getBounds());
-			elemBounds.applyTransform(worldTfrm);
+			Rect elemBounds = elem->getBounds();
+			widgetBounds.encapsulate(elemBounds);
+
+			ORect orientedElemBounds(elemBounds);
+			orientedElemBounds.applyTransform(worldTfrm);
 
-			mCachedBounds.push_back(std::make_pair(elemBounds, elem));
+			mCachedBounds.push_back(std::make_pair(orientedElemBounds, elem));
 		}
+
+		mBounds = ORect(widgetBounds);
+		mBounds.applyTransform(worldTfrm);
 	}
 
 	void GUIWidget::render(const Camera* camera, RenderContext& renderContext) const

+ 8 - 5
CamelotClient/CmEditorWindow.cpp

@@ -3,6 +3,7 @@
 #include "CmApplication.h"
 #include "CmSceneObject.h"
 #include "CmCursor.h"
+#include "BsGUIManager.h"
 #include "BsGUIWidget.h"
 #include "BsGUILabel.h"
 #include "BsGUISkin.h"
@@ -29,8 +30,9 @@ namespace BansheeEditor
 		mRenderWindow = RenderWindow::create(renderWindowDesc, gApplication().getPrimaryWindow());
 
 		HSceneObject so = SceneObject::create("EditorWindow-" + name);
-		HGUIWidget gui = so->addComponent<GUIWidget>();
-		
+		mGUI = so->addComponent<GUIWidget>();
+		GUIManager::instance().attachWidgetToWindow(mRenderWindow.get(), mGUI.get());
+
 		GameObjectHandle<UpdateCallback> updateCallback = so->addComponent<UpdateCallback>();
 
 		updateCallback->onUpdate.connect(boost::bind(&EditorWindow::update, this));
@@ -44,7 +46,7 @@ namespace BansheeEditor
 		//// DEBUG ONLY - Skin should exist externally
 		//mSkin = CM_NEW(GUISkin, GUIAlloc) GUISkin();
 
-		OverlayManager::instance().attachOverlay(camera.get(), gui.get());		
+		OverlayManager::instance().attachOverlay(camera.get(), mGUI.get());		
 
 		//GUIElementStyle labelStyle;
 		//labelStyle.font = dbgFont;
@@ -54,12 +56,13 @@ namespace BansheeEditor
 
 		//gui->setSkin(mSkin);
 		//// END DEBUG
-		gui->setSkin(&EngineGUI::instance().getSkin());
-		mDbgLabel = GUILabel::create(gui.get(), "Testing test", renderWindowDesc.width);
+		mGUI->setSkin(&EngineGUI::instance().getSkin());
+		mDbgLabel = GUILabel::create(mGUI.get(), "Testing test", renderWindowDesc.width);
 	}
 
 	EditorWindow::~EditorWindow()
 	{
+		GUIManager::instance().detachWidgetFromWindow(mRenderWindow.get(), mGUI.get());
 		mRenderWindow->destroy();
 	}
 

+ 1 - 0
CamelotClient/CmEditorWindow.h

@@ -16,6 +16,7 @@ namespace BansheeEditor
 	private:
 		CM::RenderWindowPtr mRenderWindow;
 
+		BS::HGUIWidget mGUI;
 		BS::GUILabel* mDbgLabel;
 	};
 }

+ 7 - 0
CamelotCore/Include/CmApplication.h

@@ -5,6 +5,8 @@
 #include "CmHighLevelGpuProgram.h"
 #include "CmRenderWindow.h"
 
+#include "boost/signal.hpp"
+
 namespace CamelotFramework
 {
 	class RenderWindow;
@@ -70,6 +72,11 @@ namespace CamelotFramework
 			 */
 			void* loadPlugin(const String& pluginName);
 
+			/**
+			 * @brief	Called every frame by the main loop, after scene update and before rendering.
+			 */
+			boost::signal<void()> mainLoopCallback;
+
 	private:
 		friend CM_EXPORT RenderContext& gMainRC();
 		friend CM_EXPORT SyncedRenderContext& gMainSyncedRC();

+ 3 - 0
CamelotCore/Source/CmApplication.cpp

@@ -86,6 +86,9 @@ namespace CamelotFramework
 		{
 			gSceneManager().update();
 
+			if(!mainLoopCallback.empty())
+				mainLoopCallback();
+
 			RenderSystem* renderSystem = RenderSystem::instancePtr();
 			RendererManager::instance().getActive()->renderAll();
 

+ 2 - 0
CamelotUtility/Include/CmFwdDeclUtil.h

@@ -18,6 +18,8 @@ namespace CamelotFramework {
 	class Vector2;
 	class Vector3;
 	class Vector4;
+	struct Int2;
+	class Rect;
 	class Color;
 	class DynLib;
 	class DynLibManager;

+ 24 - 0
CamelotUtility/Include/CmRect.h

@@ -32,6 +32,30 @@ namespace CamelotFramework
 			return false;
 		}
 
+		void encapsulate(const Rect& other)
+		{
+			int myRight = x + width;
+			int myBottom = y + height;
+			int otherRight = other.x + other.width;
+			int otherBottom = other.y + other.height;
+
+			if(other.x < x)
+				x = other.x;
+
+			if(other.y < y)
+				y = other.y;
+
+			if(otherRight > myRight)
+				width = otherRight - x;
+			else
+				width = myRight - x;
+
+			if(otherBottom > myBottom)
+				height = otherBottom - y;
+			else
+				height = myBottom - y;
+		}
+
 		int x, y, width, height;
 	};
 }

+ 12 - 6
CamelotUtility/Source/CmORect.cpp

@@ -11,18 +11,21 @@ namespace CamelotFramework
 		mSides[0] = Vector2((float)rect.width, 0.0f);
 		mSides[1] = Vector2(0.0f, (float)rect.height);
 
-		mSideLengths[0] = mSides[0].normalize();
-		mSideLengths[1] = mSides[1].normalize();
+		mSideLengths[0] = mSides[0].length();
+		mSideLengths[1] = mSides[1].length();
+
+		mSides[0].normalize();
+		mSides[1].normalize();
 	}
 
 	void ORect::applyTransform(const Matrix4& tfrm)
 	{
 		Vector3 oldOrigin = Vector3(mOrigin.x, mOrigin.y, 0);
 
-		Vector3 oldCornerA = oldOrigin + Vector3(mSides[0].x, mSides[0].y, 0.0f);
+		Vector3 oldCornerA = oldOrigin + Vector3(mSides[0].x * mSideLengths[0], mSides[0].y * mSideLengths[0], 0.0f);
 		Vector3 newCornerA = tfrm * oldCornerA;
 
-		Vector3 oldCornerB = oldOrigin + Vector3(mSides[1].x, mSides[1].y, 0.0f);
+		Vector3 oldCornerB = oldOrigin + Vector3(mSides[1].x * mSideLengths[1], mSides[1].y * mSideLengths[1], 0.0f);
 		Vector3 newCornerB = tfrm * oldCornerB;
 
 		Vector3 newOrigin = tfrm * oldOrigin;
@@ -35,8 +38,11 @@ namespace CamelotFramework
 		mSides[1].x = newCornerB.x - newOrigin.x;
 		mSides[1].y = newCornerB.y - newOrigin.y;
 
-		mSideLengths[0] = mSides[0].normalize();
-		mSideLengths[1] = mSides[1].normalize();
+		mSideLengths[0] = mSides[0].length();
+		mSideLengths[1] = mSides[1].length();
+
+		mSides[0].normalize();
+		mSides[1].normalize();
 	}
 
 	bool ORect::contains(const Vector2& point)

+ 2 - 3
TODO.txt

@@ -30,11 +30,10 @@ GUIWidget::updateMeshes leaks. If I leave the game running I can see memory cont
   - Keep track of window in focus
 
 Immediate TODO:
+ - Allow only one widget per window (remove direct calls to attach/detach widget, and require parent window to be provided when initializing widget)
+ - And make sure I clip widget to that window when updating bounds
  - (optional) Implement DirectDraw for drawing the bounds so I know they're correct
- - Calculate entire GUIWidget bounds and store them
- - Current way of transforming ORect from GUIWidget is almost certainly not correct
  - A way to update mesh buffers without recreating vertex/index buffers (Setting data currently does exactly that)
- - Transformable GUI elements and their bounds
 
 ------------------------------------------------------------------------------------------------
 Longterm plans: