Browse Source

Added WIP OS drag and drop functionality

Marko Pintera 12 years ago
parent
commit
3e75d9cf94

+ 2 - 0
CamelotCore/CamelotCore.vcxproj

@@ -399,6 +399,7 @@
     <ClInclude Include="Include\CmVertexDeclarationRTTI.h" />
     <ClInclude Include="Include\CmTechnique.h" />
     <ClInclude Include="Include\Win32\CmPlatformImpl.h" />
+    <ClInclude Include="Include\Win32\CmWin32DropTarget.h" />
     <ClInclude Include="Source\CmMeshRTTI.h" />
   </ItemGroup>
   <ItemGroup>
@@ -445,6 +446,7 @@
     <ClCompile Include="Source\CmGpuProgIncludeImporter.cpp" />
     <ClCompile Include="Source\CmPixelData.cpp" />
     <ClCompile Include="Source\CmPixelUtil.cpp" />
+    <ClCompile Include="Source\CmPlatform.cpp" />
     <ClCompile Include="Source\CmPlatformWndProc.cpp" />
     <ClCompile Include="Source\CmProfiler.cpp" />
     <ClCompile Include="Source\CmRenderer.cpp" />

+ 12 - 3
CamelotCore/CamelotCore.vcxproj.filters

@@ -91,6 +91,9 @@
     <Filter Include="Source Files\Core">
       <UniqueIdentifier>{402fe837-7d94-4343-a288-c8308fda8c18}</UniqueIdentifier>
     </Filter>
+    <Filter Include="Header Files\Platform">
+      <UniqueIdentifier>{d53f502a-b966-4162-a828-af2654f0408f}</UniqueIdentifier>
+    </Filter>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="Include\CmApplication.h">
@@ -459,9 +462,6 @@
     <ClInclude Include="Include\CmRenderQueue.h">
       <Filter>Header Files\Renderer</Filter>
     </ClInclude>
-    <ClInclude Include="Include\CmPlatform.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
     <ClInclude Include="Include\Win32\CmPlatformImpl.h">
       <Filter>Header Files\Win32</Filter>
     </ClInclude>
@@ -477,6 +477,12 @@
     <ClInclude Include="Include\CmCPUProfiler.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="Include\CmPlatform.h">
+      <Filter>Header Files\Platform</Filter>
+    </ClInclude>
+    <ClInclude Include="Include\Win32\CmWin32DropTarget.h">
+      <Filter>Header Files\Platform</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\CmApplication.cpp">
@@ -740,5 +746,8 @@
     <ClCompile Include="Source\CmCPUProfiler.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="Source\CmPlatform.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>

+ 0 - 5
CamelotCore/Include/CmApplication.h

@@ -91,11 +91,6 @@ namespace CamelotFramework
 
 		volatile bool mRunMainLoop;
 
-		/**
-		 * @brief	Runs the OS specific message pump.
-		 */
-		void updateMessagePump();
-
 		/**
 		 * @brief	Called when the frame finishes rendering.
 		 */

+ 38 - 0
CamelotCore/Include/CmPlatform.h

@@ -50,6 +50,44 @@ namespace CamelotFramework
 		bool mouseButtons[OSMouseButton::Count];
 		bool shift, ctrl;
 	};
+
+	enum class OSDropType
+	{
+		FileList,
+		None
+	};
+
+	class CM_EXPORT OSDropTarget
+	{
+	public:
+		boost::signal<void(INT32 x, INT32 y)> onDragOver;
+		boost::signal<void(INT32 x, INT32 y)> onDrop;
+
+		void setArea(INT32 x, INT32 y, UINT32 width, UINT32 height);
+		OSDropType getDropType() const { return mDropType; }
+
+		const Vector<WString>::type& getFileList() const { return *mFileList; }
+	private:
+		friend class Platform;
+
+		OSDropTarget(const RenderWindow* ownerWindow, INT32 x, INT32 y, UINT32 width, UINT32 height);
+		~OSDropTarget();
+
+		void clean();
+		void setFileList(const Vector<WString>::type& fileList);
+		const RenderWindow* getOwnerWindow() const { return mOwnerWindow; }
+	private:
+		INT32 mX, mY;
+		UINT32 mWidth, mHeight;
+		const RenderWindow* mOwnerWindow;
+
+		OSDropType mDropType;
+
+		union 
+		{
+			Vector<WString>::type* mFileList;
+		};
+	};
 }
 
 //Bring in the specific platform's header file

+ 4 - 0
CamelotCore/Include/CmPlatformWndProc.h

@@ -5,6 +5,10 @@
 
 namespace CamelotFramework
 {
+	/**
+	 * @note	This is separated from the main Platform because we don't want to include various Windows
+	 * 			defines in a lot of our code that includes "Platform.h"
+	 */
 	class CM_EXPORT PlatformWndProc : public Platform
 	{
 	public:

+ 1 - 0
CamelotCore/Include/CmWin32Defs.h

@@ -4,6 +4,7 @@
 #endif
 #include <windows.h>
 #include <windowsx.h>
+#include <oleidl.h>
 
 #define WM_CM_SETCAPTURE WM_USER + 101
 #define WM_CM_RELEASECAPTURE WM_USER + 102

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

@@ -19,6 +19,17 @@ namespace CamelotFramework
 		Pimpl* data;
 	};
 
+	// Encapsulate native cursor type so we can avoid including windows.h as it pollutes the global namespace
+	struct CM_EXPORT NativeDropTargetData
+	{
+		struct Pimpl;
+
+		NativeDropTargetData();
+		~NativeDropTargetData();
+
+		Pimpl* data;
+	};
+
 	struct CM_EXPORT NonClientResizeArea
 	{
 		NonClientAreaBorderType type;
@@ -171,6 +182,26 @@ namespace CamelotFramework
 		 */
 		static double queryPerformanceTimerMs();
 
+		/**
+		 * @brief	Creates a drop target that you can use for tracking OS drag and drop operations performed over
+		 * 			a certain area on the specified window.
+		 *
+		 * @param	window	The window on which to track drop operations.
+		 * @param	x	  	The x coordinate of the area to track, relative to window.
+		 * @param	y	  	The y coordinate of the area to track, relative to window.
+		 * @param	width 	The width of the area to track.
+		 * @param	height	The height of the area to track.
+		 *
+		 * @return	OSDropTarget that you will use to receive all drop data. When no longer needed make sure to destroy it with
+		 * 			destroyDropTarget().
+		 */
+		static OSDropTarget& createDropTarget(const RenderWindow* window, int x, int y, unsigned int width, unsigned int height);
+
+		/**
+		 * @brief	Destroys a drop target previously created with createDropTarget.
+		 */
+		static void destroyDropTarget(OSDropTarget& target);
+
 		/**
 		 * @brief	Message pump. Processes OS messages and returns when it's free.
 		 * 			
@@ -179,6 +210,12 @@ namespace CamelotFramework
 		 */
 		static void messagePump();
 
+		/**
+		 * @brief	Called during application start up from the sim thread.
+		 * 			Must be called before any other operations are done.
+		 */
+		static void startUp();
+
 		/**
 		 * @brief	Called once per frame from the sim thread.
 		 * 			
@@ -186,6 +223,18 @@ namespace CamelotFramework
 		 */
 		static void update();
 
+		/**
+		 * @brief	Called once per frame from the core thread.
+		 * 			
+		 * @note	Internal method.
+		 */
+		static void coreUpdate();
+
+		/**
+		 * @brief	Called during application shut down from the sim thread.
+		 */
+		static void shutDown();
+
 		// Callbacks triggered on the sim thread
 		static boost::signal<void(RenderWindow*)> onMouseLeftWindow;
 
@@ -211,6 +260,11 @@ namespace CamelotFramework
 		static bool mIsTrackingMouse;
 		static Vector<RenderWindow*>::type mMouseLeftWindows;
 
+		static NativeDropTargetData mDropTargets;
+
+		static bool mRequiresStartUp;
+		static bool mRequiresShutDown;
+
 		CM_STATIC_MUTEX(mSync);
 
 		static void win32ShowCursor();

+ 192 - 0
CamelotCore/Include/Win32/CmWin32DropTarget.h

@@ -0,0 +1,192 @@
+#pragma once
+
+#include "Shellapi.h"
+
+// This is just a helper include for CmPlatformImpl.cpp, it's not meant to be used on its own
+
+namespace CamelotFramework
+{
+	/**
+	* @brief	Called by the OS when various drag and drop actions are performed over a
+	* 			window this control is registered for.
+	* 			
+	* @note		This class queues all messages receives by the OS (from the core thread), and then executes
+	* 			the queue on sim thread. You should be wary of which methods are allowed to be called from which
+	* 			thread.
+	*/
+	class Win32DropTarget : public IDropTarget
+	{
+	public:
+		Win32DropTarget(HWND hWnd)
+			:mHWnd(hWnd), mRefCount(1), mAcceptDrag(false)
+		{ }
+
+		~Win32DropTarget()
+		{ }
+
+		void registerWithOS()
+		{
+			CoLockObjectExternal(this, TRUE, FALSE);
+			RegisterDragDrop(mHWnd, this);
+		}
+
+		void unregisterWithOS()
+		{
+			RevokeDragDrop(mHWnd);
+			CoLockObjectExternal(this, FALSE, FALSE);
+		}
+
+		HRESULT __stdcall QueryInterface(REFIID iid, void** ppvObject)
+		{
+			if(iid == IID_IDropTarget || iid == IID_IUnknown)
+			{
+				AddRef();
+				*ppvObject = this;
+				return S_OK;
+			}
+			else
+			{
+				*ppvObject = nullptr;
+				return E_NOINTERFACE;
+			}
+		}
+
+		ULONG __stdcall AddRef()
+		{
+			return InterlockedIncrement(&mRefCount);
+		} 
+
+		ULONG __stdcall Release()
+		{
+			LONG count = InterlockedDecrement(&mRefCount);
+
+			if(count == 0)
+			{
+				cm_delete(this);
+				return 0;
+			}
+			else
+			{
+				return count;
+			}
+		} 
+
+		HRESULT __stdcall DragEnter(IDataObject* pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
+		{
+			*pdwEffect = DROPEFFECT_NONE;
+
+			mAcceptDrag = isDataValid(pDataObj);
+			if(!mAcceptDrag)
+				return S_OK;
+			
+			// TODO - Queue (or set) move command to use later in update()
+
+			return S_OK;
+		}
+
+		HRESULT __stdcall DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
+		{
+			*pdwEffect = DROPEFFECT_NONE;
+
+			if(!mAcceptDrag)
+				return S_OK;
+
+			// TODO - Queue (or set) move command to use later in update()
+			
+			return S_OK;
+		} 
+
+		HRESULT __stdcall DragLeave()
+		{
+			// Do nothing
+
+			return S_OK;
+		}
+
+		HRESULT __stdcall Drop(IDataObject* pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
+		{
+			*pdwEffect = DROPEFFECT_NONE;
+
+			if(!isDataValid(pDataObj))
+				return S_OK;
+
+			Vector<WString>::type fileList = getFileListFromData(pDataObj);
+
+			// TODO - Add drop operation to a queue, and process it in update()
+
+			return S_OK;
+		}
+
+		// Sim thread only
+		void registerDropTarget(OSDropTarget* dropTarget)
+		{
+			mDropTargets.push_back(dropTarget);
+		}
+
+		void unregisterDropTarget(OSDropTarget* dropTarget)
+		{
+			auto findIter = std::find(begin(mDropTargets), end(mDropTargets), dropTarget);
+			if(findIter != mDropTargets.end())
+				mDropTargets.erase(findIter);
+		}
+
+		unsigned int getNumDropTargets() const 
+		{ 
+			return (unsigned int)mDropTargets.size(); 
+		}
+
+		/**
+		 * @brief	Called every frame by the sim thread. Internal method.
+		 */
+		void update()
+		{
+			// TODO - Process received data and notify child OSDropTargets
+		}
+	private:
+		bool isDataValid(IDataObject* data)
+		{
+			// TODO - Currently only supports file drag and drop, so only CF_HDROP is used
+			FORMATETC fmtetc = { CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+
+			return data->QueryGetData(&fmtetc) == S_OK ? true : false;
+		}
+
+		Vector<WString>::type getFileListFromData(IDataObject* data)
+		{
+			FORMATETC fmtetc = { CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+			STGMEDIUM stgmed;
+
+			Vector<WString>::type files;
+			if(data->GetData(&fmtetc, &stgmed) == S_OK)
+			{
+				PVOID data = GlobalLock(stgmed.hGlobal);
+
+				HDROP hDrop = (HDROP)data;
+				UINT numFiles = DragQueryFileW(hDrop, 0xFFFFFFFF, nullptr, 0);
+
+				files.resize(numFiles);
+				for(UINT i = 0; i < numFiles; i++)
+				{
+					UINT numChars = DragQueryFileW(hDrop, i, nullptr, 0) + 1;
+					wchar_t* buffer = (wchar_t*)cm_alloc((UINT32)numChars * sizeof(wchar_t));
+
+					DragQueryFileW(hDrop, i, buffer, numChars);
+
+					files[i] = WString(buffer);
+				}
+
+				GlobalUnlock(stgmed.hGlobal);
+				ReleaseStgMedium(&stgmed);
+			}
+
+			return files;
+		}
+
+	private:
+		Vector<OSDropTarget*>::type mDropTargets;
+
+		LONG mRefCount;
+		HWND mHWnd;
+		bool mAcceptDrag;
+	};
+}

+ 1 - 6
CamelotCore/Source/CmApplication.cpp

@@ -121,7 +121,7 @@ namespace CamelotFramework
 				mIsFrameRenderingFinished = false;
 			}
 
-			gCoreThread().queueCommand(boost::bind(&Application::updateMessagePump, this));
+			gCoreThread().queueCommand(&Platform::coreUpdate);
 			gCoreThread().queueCommand(boost::bind(&Application::endCoreProfiling, this));
 			PROFILE_CALL(mPrimaryCoreAccessor->submitToCoreThread(), "CommandSubmit");
 			gCoreThread().queueCommand(boost::bind(&Application::frameRenderingFinishedCallback, this));
@@ -139,11 +139,6 @@ namespace CamelotFramework
 		// a race condition we might run the loop one extra iteration which is acceptable
 	}
 
-	void Application::updateMessagePump()
-	{
-		Platform::messagePump();
-	}
-
 	void Application::frameRenderingFinishedCallback()
 	{
 		CM_LOCK_MUTEX(mFrameRenderingFinishedMutex);

+ 37 - 0
CamelotCore/Source/CmPlatform.cpp

@@ -0,0 +1,37 @@
+#include "CmPlatform.h"
+
+namespace CamelotFramework
+{
+	OSDropTarget::OSDropTarget(const RenderWindow* ownerWindow, INT32 x, INT32 y, UINT32 width, UINT32 height)
+		:mOwnerWindow(ownerWindow), mX(x), mY(y), mWidth(width), mHeight(height), mDropType(OSDropType::None), mFileList(nullptr)
+	{
+		
+	}
+
+	OSDropTarget::~OSDropTarget()
+	{
+		clean();
+	}
+
+	void OSDropTarget::setArea(INT32 x, INT32 y, UINT32 width, UINT32 height)
+	{
+		mX = x;
+		mY = y;
+		mWidth = width;
+		mHeight = height;
+	}
+
+	void OSDropTarget::clean()
+	{
+		if(mFileList != nullptr)
+			cm_delete(mFileList);
+	}
+
+	void OSDropTarget::setFileList(const Vector<WString>::type& fileList)
+	{
+		clean();
+
+		mFileList = cm_new<Vector<WString>::type>();
+		*mFileList = fileList;
+	}
+}

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

@@ -3,6 +3,8 @@
 #include "CmPixelUtil.h"
 #include "CmApplication.h"
 #include "CmWin32Defs.h"
+#include "CmDebug.h"
+#include "Win32/CmWin32DropTarget.h"
 
 namespace CamelotFramework
 {
@@ -25,6 +27,11 @@ namespace CamelotFramework
 	bool Platform::mIsTrackingMouse = false;
 	Vector<RenderWindow*>::type Platform::mMouseLeftWindows;
 
+	NativeDropTargetData Platform::mDropTargets;
+
+	bool Platform::mRequiresStartUp = false;
+	bool Platform::mRequiresShutDown = false;
+
 	CM_STATIC_MUTEX_CLASS_INSTANCE(mSync, Platform);
 
 	struct NativeCursorData::Pimpl
@@ -42,6 +49,23 @@ namespace CamelotFramework
 		cm_delete(data);
 	}
 
+	struct NativeDropTargetData::Pimpl
+	{
+		Map<const RenderWindow*, Win32DropTarget*>::type dropTargetsPerWindow;
+		Vector<Win32DropTarget*>::type dropTargetsToInitialize;
+		Vector<Win32DropTarget*>::type dropTargetsToDestroy;
+	};
+
+	NativeDropTargetData::NativeDropTargetData()
+	{
+		data = cm_new<Pimpl>();
+	}
+
+	NativeDropTargetData::~NativeDropTargetData()
+	{
+		cm_delete(data);
+	}
+
 	bool Platform::mIsCursorHidden = false;
 	NativeCursorData Platform::mCursor;
 	bool Platform::mUsingCustomCursor = false;
@@ -352,6 +376,54 @@ namespace CamelotFramework
 		return (double)counterValue.QuadPart / (counterFreq.QuadPart * 0.001);
 	}
 
+	OSDropTarget& Platform::createDropTarget(const RenderWindow* window, int x, int y, unsigned int width, unsigned int height)
+	{
+		Win32DropTarget* win32DropTarget = nullptr;
+		auto iterFind = mDropTargets.data->dropTargetsPerWindow.find(window);
+		if(iterFind == mDropTargets.data->dropTargetsPerWindow.end())
+		{
+			HWND hwnd;
+			window->getCustomAttribute("WINDOW", &hwnd);
+
+			win32DropTarget = cm_new<Win32DropTarget>(hwnd);
+			mDropTargets.data->dropTargetsPerWindow[window] = win32DropTarget;
+
+			{
+				CM_LOCK_MUTEX(mSync);
+				mDropTargets.data->dropTargetsToInitialize.push_back(win32DropTarget);
+			}
+		}
+		else
+			win32DropTarget = iterFind->second;
+
+		OSDropTarget* newDropTarget = new (cm_alloc<OSDropTarget>()) OSDropTarget(window, x, y, width, height);
+		win32DropTarget->registerDropTarget(newDropTarget);
+
+		return *newDropTarget;
+	}
+
+	void Platform::destroyDropTarget(OSDropTarget& target)
+	{
+		auto iterFind = mDropTargets.data->dropTargetsPerWindow.find(target.getOwnerWindow());
+		if(iterFind == mDropTargets.data->dropTargetsPerWindow.end())
+		{
+			LOGWRN("Attempting to destroy a drop target but cannot find its parent window.");
+		}
+		else
+		{
+			Win32DropTarget* win32DropTarget = iterFind->second;
+			win32DropTarget->unregisterDropTarget(&target);
+
+			if(win32DropTarget->getNumDropTargets() == 0)
+			{
+				CM_LOCK_MUTEX(mSync);
+				mDropTargets.data->dropTargetsToDestroy.push_back(win32DropTarget);
+			}
+		}
+		
+		CM_PVT_DELETE(OSDropTarget, &target);
+	}
+
 	void Platform::messagePump()
 	{
 		MSG  msg;
@@ -362,6 +434,13 @@ namespace CamelotFramework
 		}
 	}
 
+	void Platform::startUp()
+	{
+		CM_LOCK_MUTEX(mSync);
+
+		mRequiresStartUp = true;
+	}
+
 	void Platform::update()
 	{
 		Vector<RenderWindow*>::type windowsCopy;
@@ -377,6 +456,62 @@ namespace CamelotFramework
 			if(!onMouseLeftWindow.empty())
 				onMouseLeftWindow(window);
 		}
+
+		for(auto& dropTarget : mDropTargets.data->dropTargetsPerWindow)
+		{
+			dropTarget.second->update();
+		}
+	}
+
+	void Platform::coreUpdate()
+	{
+		{
+			CM_LOCK_MUTEX(mSync);
+			if(mRequiresStartUp)
+			{
+				OleInitialize(nullptr);
+				mRequiresStartUp = false;
+			}
+		}
+
+		{
+			CM_LOCK_MUTEX(mSync);
+			for(auto& dropTargetToInit : mDropTargets.data->dropTargetsToInitialize)
+			{
+				dropTargetToInit->registerWithOS();
+			}
+
+			mDropTargets.data->dropTargetsToInitialize.clear();
+		}
+
+		{
+			CM_LOCK_MUTEX(mSync);
+			for(auto& dropTargetToDestroy : mDropTargets.data->dropTargetsToDestroy)
+			{
+				dropTargetToDestroy->unregisterWithOS();
+				dropTargetToDestroy->Release();
+			}
+
+			mDropTargets.data->dropTargetsToDestroy.clear();
+		}
+
+		messagePump();
+
+		{
+			CM_LOCK_MUTEX(mSync);
+			if(mRequiresShutDown)
+			{
+				OleUninitialize();
+				mRequiresShutDown = false;
+			}
+		}
+	}
+
+	void Platform::shutDown()
+	{
+		CM_LOCK_MUTEX(mSync);
+
+		mRequiresShutDown = true;
 	}
 
 	void Platform::windowFocusReceived(RenderWindow* window)

+ 15 - 0
CamelotUtility/Include/CmMemoryAllocator.h

@@ -234,6 +234,21 @@ namespace CamelotFramework
 
 		MemoryAllocator<GenAlloc>::freeArray(ptr, count);
 	}
+
+
+/************************************************************************/
+/* 							MACRO VERSIONS                      		*/
+/* You will almost always want to use the template versions but in some */
+/* cases (private destructor) it is not possible. In which case you may	*/
+/* use these instead.												    */
+/************************************************************************/
+#define CM_PVT_DELETE(T, ptr) \
+	(ptr)->~T(); \
+	MemoryAllocator<GenAlloc>::free(ptr);
+
+#define CM_PVT_DELETE_A(T, ptr, Alloc) \
+	(ptr)->~T(); \
+	MemoryAllocator<Alloc>::free(ptr);
 }
 
 namespace CamelotFramework

+ 1 - 0
TODO.txt

@@ -9,6 +9,7 @@ LONGTERM TODO:
   - For now just create a profiler with basic measuring stats (FPS, core & sim thread time, plus times for most important systems), and ProfilerOverlay to display them
 
 WEEKEND - Finish up docking? Or get windows dock manager working
+ - FIX THE DAMN 200 WARNINGS!
 
 TODO: Viewport can be modified from the sim thread, but is used on the core thread without any syncronization mechanisms. Maybe add a method that returns VIEWPORT_DATA, and have that used on the core thread.