Browse Source

WIP: macOS port
- Added macOS implementations for all utility and core systems (untested)

Marko Pintera 8 years ago
parent
commit
a77eccf4f6
37 changed files with 4291 additions and 58 deletions
  1. 4 2
      Source/BansheeCore/Animation/BsAnimation.cpp
  2. 1 1
      Source/BansheeCore/Animation/BsAnimationManager.cpp
  3. 5 2
      Source/BansheeCore/CMakeLists.txt
  4. 30 4
      Source/BansheeCore/CMakeSources.cmake
  5. 2 2
      Source/BansheeCore/Image/BsPixelUtil.cpp
  6. 260 0
      Source/BansheeCore/MacOS/BsMacOSDropTarget.cpp
  7. 142 0
      Source/BansheeCore/MacOS/BsMacOSDropTarget.h
  8. 506 0
      Source/BansheeCore/MacOS/BsMacOSFolderMonitor.cpp
  9. 40 0
      Source/BansheeCore/MacOS/BsMacOSGamepad.cpp
  10. 870 0
      Source/BansheeCore/MacOS/BsMacOSInput.cpp
  11. 104 0
      Source/BansheeCore/MacOS/BsMacOSInput.h
  12. 41 0
      Source/BansheeCore/MacOS/BsMacOSKeyboard.cpp
  13. 41 0
      Source/BansheeCore/MacOS/BsMacOSMouse.cpp
  14. 82 0
      Source/BansheeCore/MacOS/BsMacOSPlatform.h
  15. 912 0
      Source/BansheeCore/MacOS/BsMacOSPlatform.mm
  16. 158 0
      Source/BansheeCore/MacOS/BsMacOSWindow.h
  17. 812 0
      Source/BansheeCore/MacOS/BsMacOSWindow.mm
  18. 13 3
      Source/BansheeCore/Platform/BsFolderMonitor.h
  19. 6 7
      Source/BansheeCore/Profiling/BsProfilerCPU.cpp
  20. 2 2
      Source/BansheeCore/Profiling/BsProfilerCPU.h
  21. 1 1
      Source/BansheeCore/RTTI/BsResourceRTTI.h
  22. 0 3
      Source/BansheeCore/Resources/BsResourceHandle.cpp
  23. 30 14
      Source/BansheeUtility/CMakeSources.cmake
  24. 0 0
      Source/BansheeUtility/Linux/BsLinuxCrashHandler.cpp
  25. 0 0
      Source/BansheeUtility/Linux/BsLinuxPlatformUtility.cpp
  26. 94 0
      Source/BansheeUtility/MacOS/BsMacOSCrashHandler.cpp
  27. 81 0
      Source/BansheeUtility/MacOS/BsMacOSPlatformUtility.cpp
  28. 1 1
      Source/BansheeUtility/Math/BsRect3.cpp
  29. 2 0
      Source/BansheeUtility/Reflection/BsRTTIField.h
  30. 0 3
      Source/BansheeUtility/String/BsStringID.cpp
  31. 0 0
      Source/BansheeUtility/Unix/BsUnixFileSystem.cpp
  32. 1 1
      Source/BansheeUtility/Utility/BsUUID.cpp
  33. 10 0
      Source/CMake/Common.cmake
  34. 8 1
      Source/CMake/GenerateScriptBindings.cmake
  35. 6 4
      Source/CMake/Modules/FindPhysX.cmake
  36. 7 2
      Source/CMake/Modules/Findmcs.cmake
  37. 19 5
      Source/CMakeLists.txt

+ 4 - 2
Source/BansheeCore/Animation/BsAnimation.cpp

@@ -1003,7 +1003,8 @@ namespace bs
 			}
 			}
 
 
 			mClipInfos.resize(newClips.size());
 			mClipInfos.resize(newClips.size());
-			memcpy(mClipInfos.data(), newClips.data(), sizeof(AnimationClipInfo) * newClips.size());
+			for(UINT32 i = 0; i < (UINT32)newClips.size(); i++)
+				mClipInfos[i] = newClips[i];
 		}
 		}
 		bs_frame_clear();
 		bs_frame_clear();
 	}
 	}
@@ -1047,7 +1048,8 @@ namespace bs
 					newClips.push_back(AnimationClipInfo());
 					newClips.push_back(AnimationClipInfo());
 
 
 				mClipInfos.resize(newClips.size());
 				mClipInfos.resize(newClips.size());
-				memcpy(mClipInfos.data(), newClips.data(), sizeof(AnimationClipInfo) * newClips.size());
+				for(UINT32 i = 0; i < (UINT32)newClips.size(); i++)
+					mClipInfos[i] = newClips[i];
 
 
 				mDirty |= AnimDirtyStateFlag::Layout;
 				mDirty |= AnimDirtyStateFlag::Layout;
 			}
 			}

+ 1 - 1
Source/BansheeCore/Animation/BsAnimationManager.cpp

@@ -445,7 +445,7 @@ namespace bs
 						}
 						}
 						else
 						else
 						{
 						{
-							*destNrm = { 127, 127, 127, 0 };
+							*destNrm = {{ 127, 127, 127, 0 }};
 						}
 						}
 					}
 					}
 
 

+ 5 - 2
Source/BansheeCore/CMakeLists.txt

@@ -31,8 +31,11 @@ target_link_libraries(BansheeCore PUBLIC BansheeUtility)
 ## OS libs
 ## OS libs
 if(WIN32)
 if(WIN32)
 	target_link_libraries(BansheeCore PRIVATE Winmm dinput8 xinput9_1_0 dxguid.lib)
 	target_link_libraries(BansheeCore PRIVATE Winmm dinput8 xinput9_1_0 dxguid.lib)
-else()
-	# TODO_OTHER_PLATFORMS_GO_HERE
+else(APPLE) # MacOS
+	target_link_framework(BansheeCore CoreServices)
+	target_link_framework(BansheeCore IOKit)
+	target_link_framework(BansheeCore AppKit)
+	target_link_framework(BansheeCore Carbon)
 endif()
 endif()
 
 
 # IDE specific
 # IDE specific

+ 30 - 4
Source/BansheeCore/CMakeSources.cmake

@@ -605,14 +605,14 @@ set(BS_BANSHEECORE_SRC_PLATFORM_WIN32
 	"Win32/BsWin32Gamepad.cpp"
 	"Win32/BsWin32Gamepad.cpp"
 )
 )
 
 
-set(BS_BANSHEECORE_INC_PLATFORM_UNIX
+set(BS_BANSHEECORE_INC_PLATFORM_LINUX
 	"Linux/BsLinuxPlatform.h"
 	"Linux/BsLinuxPlatform.h"
 	"Linux/BsLinuxWindow.h"
 	"Linux/BsLinuxWindow.h"
 	"Linux/BsLinuxDropTarget.h"
 	"Linux/BsLinuxDropTarget.h"
 	"Linux/BsLinuxInput.h"
 	"Linux/BsLinuxInput.h"
 )
 )
 
 
-set(BS_BANSHEECORE_SRC_PLATFORM_UNIX
+set(BS_BANSHEECORE_SRC_PLATFORM_LINUX
 	"Linux/BsLinuxPlatform.cpp"
 	"Linux/BsLinuxPlatform.cpp"
 	"Linux/BsLinuxWindow.cpp"
 	"Linux/BsLinuxWindow.cpp"
 	"Linux/BsLinuxDropTarget.cpp"
 	"Linux/BsLinuxDropTarget.cpp"
@@ -623,12 +623,33 @@ set(BS_BANSHEECORE_SRC_PLATFORM_UNIX
 	"Linux/BsLinuxKeyboard.cpp"
 	"Linux/BsLinuxKeyboard.cpp"
 )
 )
 
 
+set(BS_BANSHEECORE_INC_PLATFORM_MACOS
+	"MacOS/BsMacOSInput.h"
+	"MacOS/BsMacOSWindow.h"
+	"MacOS/BsMacOSPlatform.h"
+	"MacOS/BsMacOSDropTarget.h"
+)
+
+set(BS_BANSHEECORE_SRC_PLATFORM_MACOS
+	"MacOS/BsMacOSInput.cpp"
+	"MacOS/BsMacOSGamepad.cpp"
+	"MacOS/BsMacOSMouse.cpp"
+	"MacOS/BsMacOSKeyboard.cpp"
+	"MacOS/BsMacOSFolderMonitor.cpp"
+	"MacOS/BsMacOSDropTarget.cpp"
+	"MacOS/BsMacOSWindow.mm"
+	"MacOS/BsMacOSPlatform.mm"
+	)
+
 if(WIN32)
 if(WIN32)
 	list(APPEND BS_BANSHEECORE_INC_PLATFORM ${BS_BANSHEECORE_INC_PLATFORM_WIN32})
 	list(APPEND BS_BANSHEECORE_INC_PLATFORM ${BS_BANSHEECORE_INC_PLATFORM_WIN32})
 	list(APPEND BS_BANSHEECORE_SRC_PLATFORM ${BS_BANSHEECORE_SRC_PLATFORM_WIN32})
 	list(APPEND BS_BANSHEECORE_SRC_PLATFORM ${BS_BANSHEECORE_SRC_PLATFORM_WIN32})
 elseif(LINUX)
 elseif(LINUX)
-	list(APPEND BS_BANSHEECORE_INC_PLATFORM ${BS_BANSHEECORE_INC_PLATFORM_UNIX})
-	list(APPEND BS_BANSHEECORE_SRC_PLATFORM ${BS_BANSHEECORE_SRC_PLATFORM_UNIX})
+	list(APPEND BS_BANSHEECORE_INC_PLATFORM ${BS_BANSHEECORE_INC_PLATFORM_LINUX})
+	list(APPEND BS_BANSHEECORE_SRC_PLATFORM ${BS_BANSHEECORE_SRC_PLATFORM_LINUX})
+elseif(APPLE)
+	list(APPEND BS_BANSHEECORE_INC_PLATFORM ${BS_BANSHEECORE_INC_PLATFORM_MACOS})
+	list(APPEND BS_BANSHEECORE_SRC_PLATFORM ${BS_BANSHEECORE_SRC_PLATFORM_MACOS})
 endif()
 endif()
 
 
 source_group("Header Files\\Components" FILES ${BS_BANSHEECORE_INC_COMPONENTS})
 source_group("Header Files\\Components" FILES ${BS_BANSHEECORE_INC_COMPONENTS})
@@ -676,6 +697,11 @@ source_group("Source Files\\Image" FILES ${BS_BANSHEECORE_SRC_IMAGE})
 source_group("Header Files\\Mesh" FILES ${BS_BANSHEECORE_INC_MESH})
 source_group("Header Files\\Mesh" FILES ${BS_BANSHEECORE_INC_MESH})
 source_group("Source Files\\Mesh" FILES ${BS_BANSHEECORE_SRC_MESH})
 source_group("Source Files\\Mesh" FILES ${BS_BANSHEECORE_SRC_MESH})
 
 
+if(APPLE)
+	source_group("Header Files\\MacOS" FILES ${BS_BANSHEECORE_INC_PLATFORM_MACOS})
+	source_group("Source Files\\MacOS" FILES ${BS_BANSHEECORE_SRC_PLATFORM_MACOS})
+endif()
+
 set(BS_BANSHEECORE_SRC
 set(BS_BANSHEECORE_SRC
 	${BS_BANSHEECORE_INC_COMPONENTS}
 	${BS_BANSHEECORE_INC_COMPONENTS}
 	${BS_BANSHEECORE_INC_PHYSICS}
 	${BS_BANSHEECORE_INC_PHYSICS}

+ 2 - 2
Source/BansheeCore/Image/BsPixelUtil.cpp

@@ -1205,7 +1205,7 @@ namespace bs
 			:buffers(buffers), bufferWritePos(nullptr), bufferEnd(nullptr)
 			:buffers(buffers), bufferWritePos(nullptr), bufferEnd(nullptr)
 		{ }
 		{ }
 
 
-		virtual void beginImage(int size, int width, int height, int depth, int face, int miplevel)
+		void beginImage(int size, int width, int height, int depth, int face, int miplevel) override
 		{ 
 		{ 
 			assert(miplevel >= 0 && miplevel < (int)buffers.size());
 			assert(miplevel >= 0 && miplevel < (int)buffers.size());
 			assert((UINT32)size == buffers[miplevel]->getConsecutiveSize());
 			assert((UINT32)size == buffers[miplevel]->getConsecutiveSize());
@@ -1216,7 +1216,7 @@ namespace bs
 			bufferEnd = bufferWritePos + activeBuffer->getConsecutiveSize();
 			bufferEnd = bufferWritePos + activeBuffer->getConsecutiveSize();
 		}
 		}
 
 
-		virtual bool writeData(const void* data, int size)
+		bool writeData(const void* data, int size) override
 		{
 		{
 			assert((bufferWritePos + size) <= bufferEnd);
 			assert((bufferWritePos + size) <= bufferEnd);
 			memcpy(bufferWritePos, data, size);
 			memcpy(bufferWritePos, data, size);

+ 260 - 0
Source/BansheeCore/MacOS/BsMacOSDropTarget.cpp

@@ -0,0 +1,260 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#include "String/BsUnicode.h"
+#include "Platform/BsPlatform.h"
+#include "Platform/BsDropTarget.h"
+#include "RenderAPI/BsRenderWindow.h"
+#include "Math/BsRect2I.h"
+#include "MacOS/BsMacOSDropTarget.h"
+#include "MacOS/BsMacOSWindow.h"
+
+namespace bs
+{
+	Vector<CocoaDragAndDrop::DropArea> CocoaDragAndDrop::sDropAreas;
+	Mutex CocoaDragAndDrop::sMutex;
+	Vector<CocoaDragAndDrop::DragAndDropOp> CocoaDragAndDrop::sQueuedOperations;
+	Vector<CocoaDragAndDrop::DropAreaOp> CocoaDragAndDrop::sQueuedAreaOperations;
+
+	DropTarget::DropTarget(const RenderWindow* ownerWindow, const Rect2I& area)
+		: mArea(area), mActive(false), mOwnerWindow(ownerWindow), mDropType(DropTargetType::None)
+	{
+		CocoaDragAndDrop::registerDropTarget(this);
+	}
+
+	DropTarget::~DropTarget()
+	{
+		CocoaDragAndDrop::unregisterDropTarget(this);
+
+		_clear();
+	}
+
+	void DropTarget::setArea(const Rect2I& area)
+	{
+		mArea = area;
+
+		CocoaDragAndDrop::updateDropTarget(this);
+	}
+
+	void CocoaDragAndDrop::registerDropTarget(DropTarget* target)
+	{
+		Lock lock(sMutex);
+		sQueuedAreaOperations.push_back(DropAreaOp(target, DropAreaOpType::Register, target->getArea()));
+	}
+
+	void CocoaDragAndDrop::unregisterDropTarget(DropTarget* target)
+	{
+		Lock lock(sMutex);
+		sQueuedAreaOperations.push_back(DropAreaOp(target, DropAreaOpType::Unregister));
+	}
+
+	void CocoaDragAndDrop::updateDropTarget(DropTarget* target)
+	{
+		Lock lock(sMutex);
+		sQueuedAreaOperations.push_back(DropAreaOp(target, DropAreaOpType::Update, target->getArea()));
+	}
+
+	void CocoaDragAndDrop::update()
+	{
+		Vector<DragAndDropOp> operations;
+
+		{
+			Lock lock(sMutex);
+			std::swap(operations, sQueuedOperations);
+		}
+
+		for(auto& op : operations)
+		{
+			switch(op.type)
+			{
+				case DragAndDropOpType::Enter:
+					op.target->onEnter(op.position.x, op.position.y);
+					break;
+				case DragAndDropOpType::DragOver:
+					op.target->onDragOver(op.position.x, op.position.y);
+					break;
+				case DragAndDropOpType::Drop:
+					op.target->_setFileList(op.fileList);
+					op.target->onDrop(op.position.x, op.position.y);
+					break;
+				case DragAndDropOpType::Leave:
+					op.target->_clear();
+					op.target->onLeave();
+					break;
+			}
+		}
+	}
+
+	void CocoaDragAndDrop::coreUpdate()
+	{
+		// First handle any queued registration/unregistration
+		{
+			Lock lock(sMutex);
+
+			for(auto& entry : sQueuedAreaOperations)
+			{
+				CocoaWindow* areaWindow;
+				entry.target->_getOwnerWindow()->getCustomAttribute("COCOA_WINDOW", &areaWindow);
+
+				switch(entry.type)
+				{
+					case DropAreaOpType::Register:
+						sDropAreas.push_back(DropArea(entry.target, entry.area));
+						areaWindow->_registerForDragAndDrop();
+						break;
+					case DropAreaOpType::Unregister:
+						// Remove any operations queued for this target
+						for(auto iter = sQueuedOperations.begin(); iter !=sQueuedOperations.end();)
+						{
+							if(iter->target == entry.target)
+								iter = sQueuedOperations.erase(iter);
+							else
+								++iter;
+						}
+
+						// Remove the area
+						{
+							auto iterFind = std::find_if(sDropAreas.begin(), sDropAreas.end(), [&](const DropArea& area)
+							{
+								return area.target == entry.target;
+							});
+
+							sDropAreas.erase(iterFind);
+						}
+
+						areaWindow->_unregisterForDragAndDrop();
+
+						break;
+					case DropAreaOpType::Update:
+					{
+						auto iterFind = std::find_if(sDropAreas.begin(), sDropAreas.end(), [&](const DropArea& area)
+						{
+							return area.target == entry.target;
+						});
+
+						if (iterFind != sDropAreas.end())
+							iterFind->area = entry.area;
+					}
+						break;
+				}
+			}
+
+			sQueuedAreaOperations.clear();
+		}
+	}
+
+	bool CocoaDragAndDrop::_notifyDragEntered(CocoaWindow* window, const Vector2I& position)
+	{
+		bool eventAccepted = false;
+		for(auto& entry : sDropAreas)
+		{
+			CocoaWindow* areaWindow;
+			entry.target->_getOwnerWindow()->getCustomAttribute("COCOA_WINDOW", &areaWindow);
+			if(areaWindow != window)
+				continue;
+
+			if(entry.area.contains(position))
+			{
+				if(!entry.target->_isActive())
+				{
+					Lock lock(sMutex);
+					sQueuedOperations.push_back(DragAndDropOp(DragAndDropOpType::Enter, entry.target,
+						position));
+
+					entry.target->_setActive(true);
+				}
+
+				eventAccepted = true;
+			}
+		}
+
+		return eventAccepted;
+	}
+
+	bool CocoaDragAndDrop::_notifyDragMoved(CocoaWindow* window, const Vector2I& position)
+	{
+		bool eventAccepted = false;
+		for(auto& entry : sDropAreas)
+		{
+			CocoaWindow* areaWindow;
+			entry.target->_getOwnerWindow()->getCustomAttribute("COCOA_WINDOW", &areaWindow);
+			if (areaWindow != window)
+				continue;
+
+			if (entry.area.contains(position))
+			{
+				if (entry.target->_isActive())
+				{
+					Lock lock(sMutex);
+					sQueuedOperations.push_back(DragAndDropOp(DragAndDropOpType::DragOver, entry.target, position));
+				} else
+				{
+					Lock lock(sMutex);
+					sQueuedOperations.push_back(DragAndDropOp(DragAndDropOpType::Enter, entry.target, position));
+				}
+
+				entry.target->_setActive(true);
+				eventAccepted = true;
+			}
+			else
+			{
+				// Cursor left previously active target's area
+				if (entry.target->_isActive())
+				{
+					{
+						Lock lock(sMutex);
+						sQueuedOperations.push_back(DragAndDropOp(DragAndDropOpType::Leave, entry.target));
+					}
+
+					entry.target->_setActive(false);
+				}
+			}
+		}
+
+		return eventAccepted;
+	}
+
+	void CocoaDragAndDrop::_notifyDragLeft(CocoaWindow* window)
+	{
+		for(auto& entry : sDropAreas)
+		{
+			CocoaWindow* areaWindow;
+			entry.target->_getOwnerWindow()->getCustomAttribute("COCOA_WINDOW", &areaWindow);
+			if (areaWindow != window)
+				continue;
+
+			if(entry.target->_isActive())
+			{
+				{
+					Lock lock(sMutex);
+					sQueuedOperations.push_back(DragAndDropOp(DragAndDropOpType::Leave, entry.target));
+				}
+
+				entry.target->_setActive(false);
+			}
+		}
+	}
+
+	bool CocoaDragAndDrop::_notifyDragDropped(CocoaWindow* window, const Vector2I& position, const Vector<Path>& paths)
+	{
+		bool eventAccepted = false;
+		for(auto& entry : sDropAreas)
+		{
+			CocoaWindow* areaWindow;
+			entry.target->_getOwnerWindow()->getCustomAttribute("COCOA_WINDOW", &areaWindow);
+			if(areaWindow != window)
+				continue;
+
+			if(!entry.target->_isActive())
+				continue;
+
+			Lock lock(sMutex);
+			sQueuedOperations.push_back(DragAndDropOp(DragAndDropOpType::Drop, entry.target, position, paths));
+
+			eventAccepted = true;
+			entry.target->_setActive(false);
+		}
+
+		return eventAccepted;
+	}
+}
+

+ 142 - 0
Source/BansheeCore/MacOS/BsMacOSDropTarget.h

@@ -0,0 +1,142 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#pragma once
+
+#include "BsCorePrerequisites.h"
+#include "Math/BsVector2I.h"
+#include "Math/BsRect2I.h"
+
+namespace bs
+{
+	class CocoaWindow;
+
+	/** Handles Cocoa drag and drop functionality. */
+	class CocoaDragAndDrop
+	{
+		/** Possible states of the DND manager. */
+		enum class State
+		{
+			Inactive,
+			Entered,
+			Active
+		};
+
+		/** Type of drag and drop operation. */
+		enum class DragAndDropOpType
+		{
+			Enter,
+			DragOver,
+			Drop,
+			Leave
+		};
+
+		/**	Structure describing a drag and drop operation. */
+		struct DragAndDropOp
+		{
+			DragAndDropOp(DragAndDropOpType type, DropTarget* target)
+				:type(type), target(target)
+			{ }
+
+			DragAndDropOp(DragAndDropOpType type, DropTarget* target, const Vector2I& pos)
+				:type(type), target(target), position(pos)
+			{ }
+
+			DragAndDropOp(DragAndDropOpType type, DropTarget* target, const Vector2I& pos,
+				const Vector<Path>& fileList)
+				:type(type), target(target), position(pos), fileList(fileList)
+			{ }
+
+			DragAndDropOpType type;
+			DropTarget* target;
+			Vector2I position;
+			Vector<Path> fileList;
+		};
+
+		/** Represents a single registered drop area. */
+		struct DropArea
+		{
+			DropArea(DropTarget* target, const Rect2I& area)
+				:target(target), area(area)
+			{ }
+
+			DropTarget* target;
+			Rect2I area;
+		};
+
+		/** Type of operations that can happen to a DropArea. */
+		enum class DropAreaOpType
+		{
+			Register, /**< New DropArea is being registered. */
+			Unregister, /**< DropArea is being unregistered. */
+			Update /**< DropArea was updated. */
+		};
+
+		/** Operation that in some way modifies a DropArea. */
+		struct DropAreaOp
+		{
+			DropAreaOp(DropTarget* target, DropAreaOpType type, const Rect2I& area = Rect2I::EMPTY)
+				:target(target), area(area), type(type)
+			{ }
+
+			DropTarget* target;
+			Rect2I area;
+			DropAreaOpType type;
+		};
+
+	public:
+		/**
+		 * Triggers any drag and drop events.
+		 *
+		 * @note 	Sim thread only.
+		 */
+		static void update();
+
+		/**
+		 * Transfers information about drop areas to the core thread.
+		 *
+		 * @note 	Core thread only.
+		 */
+		static void coreUpdate();
+
+		/**
+		 * Registers a new drop target. Any further events processed will take this target into account, trigger its events
+		 * and populate its data if a drop occurs.
+		 *
+		 * @note 	Thread safe.
+		 */
+		static void registerDropTarget(DropTarget* target);
+
+		/**
+		 * Updates information about previous registered DropTarget. Call this when drop target area changes.
+		 *
+		 * @note	Thread safe.
+		 */
+		static void updateDropTarget(DropTarget* target);
+
+		/**
+		 * Unregisters a drop target. Its events will no longer be triggered.
+		 *
+		 * @note	Thread safe.
+		 */
+		static void unregisterDropTarget(DropTarget* target);
+
+		/** Triggered by Cocoa window when mouse cursor enters its content area while dragging. */
+		static bool _notifyDragEntered(CocoaWindow* window, const Vector2I& position);
+
+		/** Triggered by Cocoa window when mouse cursor moves within its content area while dragging. */
+		static bool _notifyDragMoved(CocoaWindow* window, const Vector2I& position);
+
+		/** Triggered by Cocoa window when mouse cursor leaves its content area while dragging.  */
+		static void _notifyDragLeft(CocoaWindow* window);
+
+		/** Triggered by Cocoa window when the user stops dragging (drops the items) within the window's content area. */
+		static bool _notifyDragDropped(CocoaWindow* window, const Vector2I& position, const Vector<Path>& paths);
+
+	private:
+		static Vector<DropArea> sDropAreas;
+		static Mutex sMutex;
+		static Vector<DragAndDropOp> sQueuedOperations;
+		static Vector<DropAreaOp> sQueuedAreaOperations;
+	};
+}
+

+ 506 - 0
Source/BansheeCore/MacOS/BsMacOSFolderMonitor.cpp

@@ -0,0 +1,506 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#include "Platform/BsFolderMonitor.h"
+#include "FileSystem/BsFileSystem.h"
+#include "Error/BsException.h"
+
+#include <CoreServices/CoreServices.h>
+
+namespace bs
+{
+	CFStringRef FolderMonitorMode = CFSTR("BSFolderMonitor");
+
+	enum class FileActionType
+	{
+		Added,
+		Removed,
+		Modified,
+		Renamed
+	};
+
+	struct FileAction
+	{
+		static FileAction* createAdded(const WString& fileName)
+		{
+			UINT8* bytes = (UINT8*)bs_alloc((UINT32)(sizeof(FileAction) + (fileName.size() + 1) * sizeof(WString::value_type)));
+
+			FileAction* action = (FileAction*)bytes;
+			bytes += sizeof(FileAction);
+
+			action->oldName = nullptr;
+			action->newName = (WString::value_type*)bytes;
+			action->type = FileActionType::Added;
+
+			memcpy(action->newName, fileName.data(), fileName.size() * sizeof(WString::value_type));
+			action->newName[fileName.size()] = L'\0';
+
+			return action;
+		}
+
+		static FileAction* createRemoved(const WString& fileName)
+		{
+			UINT8* bytes = (UINT8*)bs_alloc((UINT32)(sizeof(FileAction) + (fileName.size() + 1) * sizeof(WString::value_type)));
+
+			FileAction* action = (FileAction*)bytes;
+			bytes += sizeof(FileAction);
+
+			action->oldName = nullptr;
+			action->newName = (WString::value_type*)bytes;
+			action->type = FileActionType::Removed;
+
+			memcpy(action->newName, fileName.data(), fileName.size() * sizeof(WString::value_type));
+			action->newName[fileName.size()] = L'\0';
+
+			return action;
+		}
+
+		static FileAction* createModified(const WString& fileName)
+		{
+			UINT8* bytes = (UINT8*)bs_alloc((UINT32)(sizeof(FileAction) + (fileName.size() + 1) * sizeof(WString::value_type)));
+
+			FileAction* action = (FileAction*)bytes;
+			bytes += sizeof(FileAction);
+
+			action->oldName = nullptr;
+			action->newName = (WString::value_type*)bytes;
+			action->type = FileActionType::Modified;
+
+			memcpy(action->newName, fileName.data(), fileName.size() * sizeof(WString::value_type));
+			action->newName[fileName.size()] = L'\0';
+
+			return action;
+		}
+
+		static FileAction* createRenamed(const WString& oldFilename, const WString& newfileName)
+		{
+			UINT8* bytes = (UINT8*)bs_alloc((UINT32)(sizeof(FileAction) +
+													 (oldFilename.size() + newfileName.size() + 2) * sizeof(WString::value_type)));
+
+			FileAction* action = (FileAction*)bytes;
+			bytes += sizeof(FileAction);
+
+			action->oldName = (WString::value_type*)bytes;
+			bytes += (oldFilename.size() + 1) * sizeof(WString::value_type);
+
+			action->newName = (WString::value_type*)bytes;
+			action->type = FileActionType::Modified;
+
+			memcpy(action->oldName, oldFilename.data(), oldFilename.size() * sizeof(WString::value_type));
+			action->oldName[oldFilename.size()] = L'\0';
+
+			memcpy(action->newName, newfileName.data(), newfileName.size() * sizeof(WString::value_type));
+			action->newName[newfileName.size()] = L'\0';
+
+			return action;
+		}
+
+		static void destroy(FileAction* action)
+		{
+			bs_free(action);
+		}
+
+		WString::value_type* oldName;
+		WString::value_type* newName;
+		FileActionType type;
+	};
+
+	struct FolderMonitor::Pimpl
+	{
+		Vector<FolderWatchInfo*> monitorsToStart;
+		Vector<FolderWatchInfo*> monitorsToStop;
+		Vector<FolderWatchInfo*> monitors;
+
+		Vector<FileAction*> fileActions;
+		Vector<FileAction*> activeFileActions;
+
+		Mutex mainMutex;
+		Thread* workerThread;
+	};
+
+	static void watcherCallback(ConstFSEventStreamRef streamRef, void* userInfo, size_t numEvents, void* eventPaths,
+								const FSEventStreamEventFlags* eventFlags, const FSEventStreamEventId* eventIds);
+
+	struct FolderMonitor::FolderWatchInfo
+	{
+		FolderWatchInfo(
+			const Path& folderToMonitor,
+			FolderMonitor* owner,
+			bool monitorSubdirectories,
+			FolderChangeBits filter);
+		~FolderWatchInfo();
+
+		void startMonitor();
+		void stopMonitor();
+
+		Path folderToMonitor;
+		FolderMonitor* owner;
+		bool monitorSubdirectories;
+		FolderChangeBits filter;
+		FSEventStreamRef streamRef;
+		bool hasStarted;
+		UnorderedSet<FSEventStreamEventId> renameEvents;
+	};
+
+	FolderMonitor::FolderWatchInfo::FolderWatchInfo(
+		const Path& folderToMonitor,
+		FolderMonitor* owner,
+		bool monitorSubdirectories,
+		FolderChangeBits filter)
+			: folderToMonitor(folderToMonitor)
+			, owner(owner)
+			, monitorSubdirectories(monitorSubdirectories)
+			, filter(filter)
+			, streamRef(nullptr)
+			, hasStarted(false)
+	{ }
+
+	FolderMonitor::FolderWatchInfo::~FolderWatchInfo()
+	{
+		stopMonitor();
+	}
+
+	void FolderMonitor::FolderWatchInfo::startMonitor()
+	{
+		String pathString = folderToMonitor.toString();
+		CFStringRef path = CFStringCreateWithCString(kCFAllocatorDefault, pathString.c_str(), kCFStringEncodingUTF8);
+
+		CFArrayRef pathArray = CFArrayCreate(nullptr, (const void **)&path, 1, nullptr);
+		FSEventStreamContext context = {};
+		context.info = this;
+
+		CFAbsoluteTime latency = 0.1f;
+		streamRef = FSEventStreamCreate(
+				kCFAllocatorDefault,
+				&watcherCallback,
+				&context,
+				pathArray,
+				kFSEventStreamEventIdSinceNow,
+				latency,
+				kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagFileEvents);
+
+		CFRelease(pathArray);
+
+		if (streamRef)
+		{
+			FSEventStreamScheduleWithRunLoop(streamRef, CFRunLoopGetMain(), FolderMonitorMode);
+			if(FSEventStreamStart(streamRef))
+				hasStarted = true;
+		}
+
+		CFRelease(path);
+ 	}
+
+	void FolderMonitor::FolderWatchInfo::stopMonitor()
+	{
+		if(!streamRef)
+			return;
+
+		if(hasStarted)
+		{
+			FSEventStreamStop(streamRef);
+			hasStarted = false;
+		}
+
+		FSEventStreamInvalidate(streamRef);
+		FSEventStreamRelease(streamRef);
+	}
+
+	static void watcherCallback(ConstFSEventStreamRef streamRef, void* userInfo, size_t numEvents, void* eventPaths,
+								const FSEventStreamEventFlags* eventFlags, const FSEventStreamEventId* eventIds)
+	{
+		auto* watcher = (FolderMonitor::FolderWatchInfo*)userInfo;
+		FolderMonitor::Pimpl* folderData = watcher->owner->_getPrivateData();
+
+		auto paths = (CFArrayRef)eventPaths;
+		CFIndex length = CFArrayGetCount(paths);
+
+		for(CFIndex i = 0; i < length; i++)
+		{
+			auto pathEntry = (CFStringRef)CFArrayGetValueAtIndex(paths, i);
+			Path path = CFStringGetCStringPtr(pathEntry, kCFStringEncodingUTF8);
+
+			CFIndex pathLength = CFStringGetLength(pathEntry);
+			if(pathLength == 0)
+				continue;
+
+			Lock lock(folderData->mainMutex);
+
+			// If not monitoring subdirectories, ignore paths that aren't direct descendants of the root path
+			if(!watcher->monitorSubdirectories)
+			{
+				if(path.getParent() != watcher->folderToMonitor)
+					continue;
+			}
+
+			FSEventStreamEventFlags flags = eventFlags[i];
+
+			bool isFile = (flags & kFSEventStreamEventFlagItemIsFile) != 0;
+			bool wasCreated = (flags & kFSEventStreamEventFlagItemCreated) != 0;
+			bool wasModified = (flags & kFSEventStreamEventFlagItemModified) != 0;
+			bool wasRemoved = (flags & kFSEventStreamEventFlagItemRemoved) != 0;
+
+			// Rename events get translated to create/remove events
+			bool wasRenamed = (flags & kFSEventStreamEventFlagItemRenamed) != 0;
+
+			if(wasRenamed)
+			{
+				auto iterFind = watcher->renameEvents.find(eventIds[i]);
+				if (iterFind == watcher->renameEvents.end())
+				{
+					wasRemoved = true;
+					watcher->renameEvents.insert(eventIds[i]);
+				}
+				else
+				{
+					wasCreated = true;
+					watcher->renameEvents.erase(eventIds[i]);
+				}
+			}
+
+			// File/folder was added
+			if(wasCreated)
+			{
+				if (!isFile)
+				{
+					if (watcher->filter.isSet(FolderChangeBit::DirName))
+						folderData->fileActions.push_back(FileAction::createAdded(path.toWString()));
+				}
+				else
+				{
+					if (watcher->filter.isSet(FolderChangeBit::FileName))
+						folderData->fileActions.push_back(FileAction::createAdded(path.toWString()));
+				}
+			}
+
+			// File/folder was removed
+			if(wasRemoved)
+			{
+				if(!isFile)
+				{
+					if(watcher->filter.isSet(FolderChangeBit::DirName))
+						folderData->fileActions.push_back(FileAction::createRemoved(path.toWString()));
+				}
+				else
+				{
+					if(watcher->filter.isSet(FolderChangeBit::FileName))
+						folderData->fileActions.push_back(FileAction::createRemoved(path.toWString()));
+				}
+			}
+
+			// File was modified
+			if(wasModified && watcher->filter.isSet(FolderChangeBit::FileWrite))
+			{
+				folderData->fileActions.push_back(FileAction::createModified(path.toWString()));
+			}
+		}
+	}
+
+
+	class FolderMonitor::FileNotifyInfo
+	{
+	};
+
+	FolderMonitor::FolderMonitor()
+	{
+		m = bs_new<Pimpl>();
+		m->workerThread = nullptr;
+	}
+
+	FolderMonitor::~FolderMonitor()
+	{
+		stopMonitorAll();
+
+		// No need for mutex since we know worker thread is shut down by now
+		for(auto& action : m->fileActions)
+			FileAction::destroy(action);
+
+		bs_delete(m);
+	}
+
+	void FolderMonitor::startMonitor(const Path& folderPath, bool subdirectories, FolderChangeBits changeFilter)
+	{
+		if(!FileSystem::isDirectory(folderPath))
+		{
+			LOGERR("Provided path \"" + folderPath.toString() + "\" is not a directory.");
+			return;
+		}
+
+		// Check if there is overlap with existing monitors
+		for(auto& monitor : m->monitors)
+		{
+			// Identical monitor exists
+			if(monitor->folderToMonitor.equals(folderPath))
+			{
+				LOGWRN("Folder is already monitored, cannot monitor it again.");
+				return;
+			}
+
+			// This directory is part of a directory that's being monitored
+			if(monitor->monitorSubdirectories && folderPath.includes(monitor->folderToMonitor))
+			{
+				LOGWRN("Folder is already monitored, cannot monitor it again.");
+				return;
+			}
+
+			// This directory would include a directory of another monitor
+			if(subdirectories && monitor->folderToMonitor.includes(folderPath))
+			{
+				LOGWRN("Cannot add a recursive monitor as it conflicts with a previously monitored path.");
+				return;
+			}
+		}
+
+		auto watchInfo = bs_new<FolderWatchInfo>(folderPath, this, subdirectories, changeFilter);
+
+		// Register and start the monitor
+		{
+			Lock lock(m->mainMutex);
+
+			m->monitorsToStart.push_back(watchInfo);
+			m->monitors.push_back(watchInfo);
+		}
+
+		// Start the worker thread if it isn't already
+		if(m->workerThread == nullptr)
+		{
+			m->workerThread = bs_new<Thread>(std::bind(&FolderMonitor::workerThreadMain, this));
+
+			if(m->workerThread == nullptr)
+				LOGERR("Failed to create a new worker thread for folder monitoring");
+		}
+	}
+
+	void FolderMonitor::stopMonitor(const Path& folderPath)
+	{
+		auto findIter = std::find_if(m->monitors.begin(), m->monitors.end(),
+				[&](const FolderWatchInfo* x) { return x->folderToMonitor == folderPath; });
+
+		if(findIter != m->monitors.end())
+		{
+			// Special case if this is the last monitor
+			if(m->monitors.size() == 1)
+				stopMonitorAll();
+			else
+			{
+				Lock lock(m->mainMutex);
+				FolderWatchInfo* watchInfo = *findIter;
+
+				watchInfo->stopMonitor();
+
+				m->monitorsToStop.push_back(watchInfo);
+				m->monitors.erase(findIter);
+			}
+		}
+	}
+
+	void FolderMonitor::stopMonitorAll()
+	{
+		{
+			Lock lock(m->mainMutex);
+
+			// Remove all watches (this will also wake up the thread)
+			for (auto& watchInfo : m->monitors)
+			{
+				watchInfo->stopMonitor();
+
+				m->monitorsToStop.push_back(watchInfo);
+			}
+
+			m->monitors.clear();
+		}
+
+		// Wait for the thread to shutdown
+		if(m->workerThread != nullptr)
+		{
+			m->workerThread->join();
+			bs_delete(m->workerThread);
+			m->workerThread = nullptr;
+		}
+	}
+
+	void FolderMonitor::workerThreadMain()
+	{
+		while(true)
+		{
+			// Start up any newly added monitors
+			{
+				Lock lock(m->mainMutex);
+
+				for(auto& entry : m->monitorsToStart)
+					entry->startMonitor();
+
+				m->monitorsToStart.clear();
+			}
+
+			// Run the loop in order to receive events
+			INT32 result = CFRunLoopRunInMode(FolderMonitorMode, 0.1f, false);
+
+			// Delete any stopped monitors
+			{
+				Lock lock(m->mainMutex);
+
+				for (auto& entry : m->monitorsToStop)
+					bs_delete(entry);
+
+				m->monitorsToStop.clear();
+			}
+
+			// All input sources removed, or explicitly stopped, bail
+			if((result == kCFRunLoopRunStopped) || (result == kCFRunLoopRunFinished))
+				break;
+
+			// It's possible some system registered an input source with our loop, in which case the above check will not
+			// work. Instead check if there are any monitors left.
+			// Note: In this case we may also pay a 0.1 second timeout cost, since we don't explicitly wake the run loop.
+			//       Ideally we would also wake the run loop from the main thread so it is able to exit immediately.
+			{
+				Lock lock(m->mainMutex);
+
+				if(m->monitors.empty())
+					break;
+			}
+		}
+	}
+
+	void FolderMonitor::handleNotifications(FileNotifyInfo& notifyInfo, FolderWatchInfo& watchInfo)
+	{
+		// Do nothing
+	}
+
+	void FolderMonitor::_update()
+	{
+		{
+			Lock lock(m->mainMutex);
+
+			std::swap(m->fileActions, m->activeFileActions);
+		}
+
+		for(auto& action : m->activeFileActions)
+		{
+			switch (action->type)
+			{
+			case FileActionType::Added:
+				if (!onAdded.empty())
+					onAdded(Path(action->newName));
+				break;
+			case FileActionType::Removed:
+				if (!onRemoved.empty())
+					onRemoved(Path(action->newName));
+				break;
+			case FileActionType::Modified:
+				if (!onModified.empty())
+					onModified(Path(action->newName));
+				break;
+			case FileActionType::Renamed:
+				if (!onRenamed.empty())
+					onRenamed(Path(action->oldName), Path(action->newName));
+				break;
+			}
+
+			FileAction::destroy(action);
+		}
+
+		m->activeFileActions.clear();
+	}
+}
+

+ 40 - 0
Source/BansheeCore/MacOS/BsMacOSGamepad.cpp

@@ -0,0 +1,40 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#include "Input/BsGamepad.h"
+#include "Input/BsInput.h"
+#include "MacOS/BsMacOSInput.h"
+
+namespace bs
+{
+	/** Contains private data for the MacOS Gamepad implementation. */
+	struct Gamepad::Pimpl
+	{
+		HIDManager* hid;
+		IOHIDDeviceRef ref;
+		bool hasInputFocus = true;
+	};
+
+	Gamepad::Gamepad(const String& name, const GamepadInfo& gamepadInfo, Input* owner)
+		: mName(name), mOwner(owner)
+	{
+		m = bs_new<Pimpl>();
+		m->hid = gamepadInfo.hid;
+		m->ref = gamepadInfo.deviceRef;
+	}
+
+	Gamepad::~Gamepad()
+	{
+		bs_delete(m);
+	}
+
+	void Gamepad::capture()
+	{
+		m->hid->capture(m->ref, !m->hasInputFocus);
+	}
+
+	void Gamepad::changeCaptureContext(UINT64 windowHandle)
+	{
+		m->hasInputFocus = windowHandle != (UINT64)-1;
+	}
+}
+

+ 870 - 0
Source/BansheeCore/MacOS/BsMacOSInput.cpp

@@ -0,0 +1,870 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#include "Input/BsInput.h"
+#include "MacOS/BsMacOSInput.h"
+#include "Input/BsMouse.h"
+#include "Input/BsKeyboard.h"
+#include "Input/BsGamepad.h"
+
+namespace bs
+{
+	/**
+	 * Helper method that creates a dictionary that is used for matching a specific set of devices (matching the provided
+	 * page and usage values, as USB HID values), used for initializing a HIDManager.
+	 */
+	static CFDictionaryRef createHIDDeviceMatchDictionary(UINT32 page, UINT32 usage)
+	{
+		CFDictionaryRef output = nullptr;
+		CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page);
+		CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
+		const void* keys[2] = { (void*) CFSTR(kIOHIDDeviceUsagePageKey), (void*) CFSTR(kIOHIDDeviceUsageKey) };
+		const void* values[2] = { (void*) pageNumRef, (void*) usageNumRef };
+
+		if (pageNumRef && usageNumRef)
+		{
+			output = CFDictionaryCreate(kCFAllocatorDefault, keys, values, 2, &kCFTypeDictionaryKeyCallBacks,
+					&kCFTypeDictionaryValueCallBacks);
+		}
+
+		if (pageNumRef)
+			CFRelease(pageNumRef);
+
+		if (usageNumRef)
+			CFRelease(usageNumRef);
+
+		return output;
+	}
+
+	/** Returns the name of the run loop used for processing events for the specified category of input devices. */
+	static CFStringRef getRunLoopMode(HIDType type)
+	{
+		static CFStringRef KeyboardMode = CFSTR("BSKeyboard");
+		static CFStringRef MouseMode = CFSTR("BSMouse");
+		static CFStringRef GamepadMode = CFSTR("BSGamepad");
+
+		switch(type)
+		{
+		case HIDType::Keyboard:
+			return KeyboardMode;
+		case HIDType::Mouse:
+			return MouseMode;
+		case HIDType::Gamepad:
+			return GamepadMode;
+		}
+
+		return nullptr;
+	}
+
+	static void HIDAddElements(CFArrayRef array, HIDDevice* device);
+
+	/**
+	 * Callback called when enumerating an array of HID elements. Each element's information is parsed and stored in the
+	 * owner HIDDevice (passed through @p passthrough parameter).
+	 *
+	 * @param[in] value 		IOHIDElementRef of the current element.
+	 * @param[in] passthrough 	Pointer to element's parent HIDDevice.
+	 */
+	static void HIDAddElement(const void* value, void* passthrough)
+	{
+		auto device = (HIDDevice*)passthrough;
+		auto elemRef = (IOHIDElementRef) value;
+
+		if(!elemRef)
+			return;
+
+		CFTypeID typeID = CFGetTypeID(elemRef);
+		if(typeID != IOHIDElementGetTypeID())
+			return;
+
+		IOHIDElementType type = IOHIDElementGetType(elemRef);
+		switch (type)
+		{
+		case kIOHIDElementTypeInput_Button:
+		case kIOHIDElementTypeInput_Axis:
+		case kIOHIDElementTypeInput_Misc:
+		case kIOHIDElementTypeInput_ScanCodes:
+			break;
+		case kIOHIDElementTypeCollection:
+		{
+			CFArrayRef array = IOHIDElementGetChildren(elemRef);
+			if(array)
+				HIDAddElements(array, device);
+		}
+			return;
+		default:
+			return;
+		}
+
+		UINT32 usagePage = IOHIDElementGetUsagePage(elemRef);
+		UINT32 usage = IOHIDElementGetUsage(elemRef);
+
+		enum ElemState { IsUnknown, IsButton, IsAxis, IsHat };
+		ElemState state = IsUnknown;
+
+		switch(usagePage)
+		{
+		case kHIDPage_Button:
+		case kHIDPage_KeyboardOrKeypad:
+			state = IsButton;
+			break;
+		case kHIDPage_GenericDesktop:
+			switch(usage)
+			{
+			case kHIDUsage_GD_Start:
+			case kHIDUsage_GD_Select:
+			case kHIDUsage_GD_SystemMainMenu:
+			case kHIDUsage_GD_DPadUp:
+			case kHIDUsage_GD_DPadDown:
+			case kHIDUsage_GD_DPadRight:
+			case kHIDUsage_GD_DPadLeft:
+				state = IsButton;
+				break;
+			case kHIDUsage_GD_X:
+			case kHIDUsage_GD_Y:
+			case kHIDUsage_GD_Z:
+			case kHIDUsage_GD_Rx:
+			case kHIDUsage_GD_Ry:
+			case kHIDUsage_GD_Rz:
+			case kHIDUsage_GD_Slider:
+			case kHIDUsage_GD_Dial:
+			case kHIDUsage_GD_Wheel:
+				state = IsAxis;
+				break;
+			case kHIDUsage_GD_Hatswitch:
+				state = IsHat;
+				break;
+			default:
+				break;
+			};
+			break;
+		case kHIDPage_Simulation:
+			switch(usage)
+			{
+			case kHIDUsage_Sim_Rudder:
+			case kHIDUsage_Sim_Throttle:
+			case kHIDUsage_Sim_Accelerator:
+			case kHIDUsage_Sim_Brake:
+				state = IsAxis;
+			default:
+				break;
+			}
+			break;
+		default:
+			break;
+		};
+
+		Vector<HIDElement>* elements = nullptr;
+		switch(state)
+		{
+		case IsButton:
+			elements = &device->buttons;
+			break;
+		case IsAxis:
+			elements = &device->axes;
+			break;
+		case IsHat:
+			elements = &device->hats;
+			break;
+		default:
+			break;
+		}
+
+		if(elements != nullptr)
+		{
+			HIDElement element;
+			element.usage = usage;
+			element.ref = elemRef;
+			element.cookie = IOHIDElementGetCookie(elemRef);
+			element.min = element.detectedMin = (INT32)IOHIDElementGetLogicalMin(elemRef);
+			element.max = element.detectedMax = (INT32)IOHIDElementGetLogicalMax(elemRef);
+
+			auto iterFind = std::find_if(elements->begin(), elements->end(),
+					[&element](const HIDElement& v)
+					{
+						return v.cookie == element.cookie;
+					});
+
+			if(iterFind == elements->end())
+				elements->push_back(element);
+		}
+	}
+
+	/** Parses information about and registers all HID elements in @p array with the @p device. */
+	void HIDAddElements(CFArrayRef array, HIDDevice* device)
+	{
+		CFRange range = { 0, CFArrayGetCount(array) };
+		CFArrayApplyFunction(array, range, HIDAddElement, device);
+	}
+
+	/**
+	 * Callback triggered when a HID manager detects a new device. Also called for existing devices when HID manager is
+	 * first initialized.
+	 */
+	static void HIDDeviceAddedCallback(void* context, IOReturn result, void* sender, IOHIDDeviceRef device)
+	{
+		auto data = (HIDData*)context;
+
+		for(auto& entry : data->devices)
+		{
+			if(entry.ref == device)
+				return; // Duplicate
+		}
+
+		HIDDevice newDevice;
+		newDevice.ref = device;
+
+		// Parse device name
+		CFTypeRef propertyRef = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
+		if(!propertyRef)
+			propertyRef = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDManufacturerKey));
+
+		if(propertyRef)
+		{
+			char buffer[256];
+			if(CFStringGetCString((CFStringRef)propertyRef, buffer,	sizeof(buffer), kCFStringEncodingUTF8))
+				newDevice.name = String(buffer);
+		}
+
+		// Parse device elements
+		CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, nullptr, kIOHIDOptionsTypeNone);
+		if(elements)
+		{
+			HIDAddElements(elements, &newDevice);
+			CFRelease(elements);
+		}
+
+		// Create a queue
+		newDevice.queueRef = IOHIDQueueCreate(kCFAllocatorDefault, device, 128, kIOHIDOptionsTypeNone);
+
+		for(auto& button : newDevice.buttons)
+			IOHIDQueueAddElement(newDevice.queueRef, button.ref);
+
+		for(auto& hat : newDevice.hats)
+			IOHIDQueueAddElement(newDevice.queueRef, hat.ref);
+
+		IOHIDQueueStart(newDevice.queueRef);
+
+		// Assign a device ID
+		if(data->type == HIDType::Gamepad)
+		{
+			auto freeId = (UINT32)-1;
+
+			auto numDevices = (UINT32)data->devices.size();
+			for(UINT32 i = 0; i < numDevices; i++)
+			{
+				bool validId = true;
+				for(auto& entry : data->devices)
+				{
+					if(entry.id == i)
+					{
+						validId = false;
+						break;
+					}
+				}
+
+				if(validId)
+				{
+					freeId = i;
+					break;
+				}
+			}
+
+			if(freeId == (UINT32)-1)
+				freeId = numDevices;
+
+			newDevice.id = freeId;
+		}
+		else // All keyboard/mouse devices are coalesced into a single device
+			newDevice.id = 0;
+
+		data->devices.push_back(newDevice);
+
+		// Register the gamepad device with Input manager
+		if(data->type == HIDType::Gamepad)
+		{
+			InputPrivateData* pvtData = data->owner->_getPrivateData();
+
+			GamepadInfo gamepadInfo;
+			gamepadInfo.name = newDevice.name;
+			gamepadInfo.id = newDevice.id;
+			gamepadInfo.deviceRef = newDevice.ref;
+			gamepadInfo.hid = nullptr;
+
+			pvtData->gamepadInfos.push_back(gamepadInfo);
+		}
+	}
+
+	/** Callback triggered when an input device is removed. */
+	static void HIDDeviceRemovedCallback(void* context, IOReturn result, void* sender, IOHIDDeviceRef device)
+	{
+		auto data = (HIDData*)context;
+
+		auto iterFind = std::find_if(data->devices.begin(), data->devices.end(),
+				[&device](const HIDDevice& v)
+				{
+					return v.ref == device;
+				});
+
+		if(iterFind != data->devices.end())
+		{
+			IOHIDQueueStop(iterFind->queueRef);
+			CFRelease(iterFind->queueRef);
+
+			// Unregister the gamepad device from the Input manager
+			if(data->type == HIDType::Gamepad)
+			{
+				InputPrivateData* pvtData = data->owner->_getPrivateData();
+
+				UINT32 deviceId = iterFind->id;
+				auto iterFind2 = std::find_if(
+					pvtData->gamepadInfos.begin(),
+					pvtData->gamepadInfos.end(),
+					[deviceId](const GamepadInfo& info)
+					{
+						return info.id == deviceId;
+					});
+
+				if(iterFind2 != pvtData->gamepadInfos.end())
+					pvtData->gamepadInfos.erase(iterFind2);
+			}
+
+			data->devices.erase(iterFind);
+		}
+	}
+
+	/** Reads the current value of a particular HID element (e.g. button, axis). */
+	static INT32 HIDGetElementValue(const HIDDevice &device, const HIDElement &element)
+	{
+		IOHIDValueRef valueRef;
+		if(IOHIDDeviceGetValue(device.ref, element.ref, &valueRef) != kIOReturnSuccess)
+			return 0;
+
+		auto value = (INT32) IOHIDValueGetIntegerValue(valueRef);
+
+		if(value < element.detectedMin)
+			element.detectedMin = value;
+
+		if(value > element.detectedMax)
+			element.detectedMax = value;
+
+		return value;
+	}
+
+	/**
+	 * Reads the current value of a particular HID element (e.g. button, axis) and converts the value so it fits
+	 * the provided [min, max] range.
+	 */
+	static INT32 HIDGetElementValueScaled(const HIDDevice &device, const HIDElement &element, INT32 min, INT32 max)
+	{
+		INT32 value = HIDGetElementValue(device, element);
+
+		float deviceRange = element.detectedMax - element.detectedMin;
+		if(deviceRange == 0.0f)
+			return value;
+
+		float normalizedRange = (value - element.detectedMin) / deviceRange;
+
+		float targetRange = max - min;
+		return (INT32)(normalizedRange * targetRange) + min;
+	}
+
+	/** Converts a keyboard scan key (as reported by the HID manager) into engine's ButtonCode. */
+	static ButtonCode scanCodeToKeyCode(UINT32 scanCode)
+	{
+		switch(scanCode)
+		{
+		case 0x04: return BC_A;
+		case 0x05: return BC_B;
+		case 0x06: return BC_C;
+		case 0x07: return BC_D;
+		case 0x08: return BC_E;
+		case 0x09: return BC_F;
+		case 0x0a: return BC_G;
+		case 0x0b: return BC_H;
+		case 0x0c: return BC_I;
+		case 0x0d: return BC_J;
+		case 0x0e: return BC_K;
+		case 0x0f: return BC_L;
+		case 0x10: return BC_M;
+		case 0x11: return BC_N;
+		case 0x12: return BC_O;
+		case 0x13: return BC_P;
+		case 0x14: return BC_Q;
+		case 0x15: return BC_R;
+		case 0x16: return BC_S;
+		case 0x17: return BC_T;
+		case 0x18: return BC_U;
+		case 0x19: return BC_V;
+		case 0x1a: return BC_W;
+		case 0x1b: return BC_X;
+		case 0x1c: return BC_Y;
+		case 0x1d: return BC_Z;
+
+		case 0x1e: return BC_1;
+		case 0x1f: return BC_2;
+		case 0x20: return BC_3;
+		case 0x21: return BC_4;
+		case 0x22: return BC_5;
+		case 0x23: return BC_6;
+		case 0x24: return BC_7;
+		case 0x25: return BC_8;
+		case 0x26: return BC_9;
+		case 0x27: return BC_0;
+
+		case 0x28: return BC_RETURN;
+		case 0x29: return BC_ESCAPE;
+		case 0x2a: return BC_BACK;
+		case 0x2b: return BC_TAB;
+		case 0x2c: return BC_SPACE;
+		case 0x2d: return BC_MINUS;
+		case 0x2e: return BC_EQUALS;
+		case 0x2f: return BC_LBRACKET;
+		case 0x30: return BC_RBRACKET;
+		case 0x31: return BC_BACKSLASH;
+		case 0x32: return BC_GRAVE;
+		case 0x33: return BC_SEMICOLON;
+		case 0x34: return BC_APOSTROPHE;
+		case 0x35: return BC_GRAVE;
+		case 0x36: return BC_COMMA;
+		case 0x37: return BC_PERIOD;
+		case 0x38: return BC_SLASH;
+		case 0x39: return BC_CAPITAL;
+
+		case 0x3a: return BC_F1;
+		case 0x3b: return BC_F2;
+		case 0x3c: return BC_F3;
+		case 0x3d: return BC_F4;
+		case 0x3e: return BC_F5;
+		case 0x3f: return BC_F6;
+		case 0x40: return BC_F7;
+		case 0x41: return BC_F8;
+		case 0x42: return BC_F9;
+		case 0x43: return BC_F10;
+		case 0x44: return BC_F11;
+		case 0x45: return BC_F12;
+
+		case 0x46: return BC_SYSRQ;
+		case 0x47: return BC_SCROLL;
+		case 0x48: return BC_PAUSE;
+		case 0x49: return BC_INSERT;
+		case 0x4a: return BC_HOME;
+		case 0x4b: return BC_PGUP;
+		case 0x4c: return BC_DELETE;
+		case 0x4d: return BC_END;
+		case 0x4e: return BC_PGDOWN;
+		case 0x4f: return BC_RIGHT;
+		case 0x50: return BC_LEFT;
+		case 0x51: return BC_DOWN;
+		case 0x52: return BC_UP;
+
+		case 0x53: return BC_NUMLOCK;
+		case 0x54: return BC_DIVIDE;
+		case 0x55: return BC_MULTIPLY;
+		case 0x56: return BC_SUBTRACT;
+		case 0x57: return BC_ADD;
+		case 0x58: return BC_NUMPADENTER;
+		case 0x59: return BC_NUMPAD1;
+		case 0x5a: return BC_NUMPAD2;
+		case 0x5b: return BC_NUMPAD3;
+		case 0x5c: return BC_NUMPAD4;
+		case 0x5d: return BC_NUMPAD5;
+		case 0x5e: return BC_NUMPAD6;
+		case 0x5f: return BC_NUMPAD7;
+		case 0x60: return BC_NUMPAD8;
+		case 0x61: return BC_NUMPAD9;
+		case 0x62: return BC_NUMPAD0;
+		case 0x63: return BC_NUMPADCOMMA;
+
+		case 0x64: return BC_OEM_102;
+		case 0x66: return BC_POWER;
+		case 0x67: return BC_NUMPADEQUALS;
+
+		case 0x68: return BC_F13;
+		case 0x69: return BC_F14;
+		case 0x6a: return BC_F15;
+
+		case 0x78: return BC_STOP;
+		case 0x7f: return BC_MUTE;
+		case 0x80: return BC_VOLUMEUP;
+		case 0x81: return BC_VOLUMEDOWN;
+		case 0x85: return BC_NUMPADCOMMA;
+		case 0x86: return BC_NUMPADEQUALS;
+		case 0x89: return BC_YEN;
+
+		case 0xe0: return BC_LCONTROL;
+		case 0xe1: return BC_LSHIFT;
+		case 0xe2: return BC_LMENU;
+		case 0xe3: return BC_LWIN;
+		case 0xe4: return BC_RCONTROL;
+		case 0xe5: return BC_RSHIFT;
+		case 0xe6: return BC_RMENU;
+		case 0xe7: return BC_RWIN;
+
+		case 0xe8: return BC_PLAYPAUSE;
+		case 0xe9: return BC_MEDIASTOP;
+		case 0xea: return BC_PREVTRACK;
+		case 0xeb: return BC_NEXTTRACK;
+		case 0xed: return BC_VOLUMEUP;
+		case 0xee: return BC_VOLUMEDOWN;
+		case 0xef: return BC_MUTE;
+		case 0xf0: return BC_WEBSEARCH;
+		case 0xf1: return BC_WEBBACK;
+		case 0xf2: return BC_WEBFORWARD;
+		case 0xf3: return BC_WEBSTOP;
+		case 0xf4: return BC_WEBSEARCH;
+		case 0xf8: return BC_SLEEP;
+		case 0xf9: return BC_WAKE;
+		case 0xfb: return BC_CALCULATOR;
+
+		default:
+			return BC_UNASSIGNED;
+		}
+	}
+
+	HIDManager::HIDManager(HIDType type, Input* input)
+	{
+		mData.type = type;
+		mData.owner = input;
+
+		mHIDManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDManagerOptionNone);
+		if(mHIDManager == nullptr)
+			return;
+
+		if(IOHIDManagerOpen(mHIDManager, kIOHIDOptionsTypeNone) != kIOReturnSuccess)
+			return;
+
+		UINT32 numEntries = 0;
+		const void* entries[3];
+
+		switch (type)
+		{
+		case HIDType::Keyboard:
+			entries[0] = createHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard);
+			numEntries = 1;
+			break;
+		case HIDType::Mouse:
+			entries[0] = createHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Mouse);
+			numEntries = 1;
+			break;
+		case HIDType::Gamepad:
+			entries[0] = createHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick);
+			entries[1] = createHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad);
+			entries[2] = createHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController);
+			numEntries = 3;
+			break;
+		}
+
+		CFArrayRef entryArray = CFArrayCreate(kCFAllocatorDefault, entries, numEntries, &kCFTypeArrayCallBacks);
+
+		IOHIDManagerSetDeviceMatchingMultiple(mHIDManager, entryArray);
+		IOHIDManagerRegisterDeviceMatchingCallback(mHIDManager, HIDDeviceAddedCallback, &mData);
+		IOHIDManagerRegisterDeviceRemovalCallback(mHIDManager, HIDDeviceRemovedCallback, &mData);
+
+		CFStringRef runLoopMode = getRunLoopMode(type);
+		IOHIDManagerScheduleWithRunLoop(mHIDManager, CFRunLoopGetCurrent(), runLoopMode);
+
+		while(CFRunLoopRunInMode(runLoopMode, 0, TRUE) == kCFRunLoopRunHandledSource)
+		{ /* Do nothing */ }
+
+		for (UINT32 i = 0; i < numEntries; i++)
+		{
+			if (entries[i])
+				CFRelease((CFTypeRef) entries[i]);
+		}
+
+		CFRelease(entryArray);
+	}
+
+	HIDManager::~HIDManager()
+	{
+		for(auto& device : mData.devices)
+		{
+			IOHIDQueueStop(device.queueRef);
+			CFRelease(device.queueRef);
+		}
+
+		CFStringRef runLoopMode = getRunLoopMode(mData.type);
+		IOHIDManagerUnscheduleFromRunLoop(mHIDManager, CFRunLoopGetCurrent(), runLoopMode);
+
+		IOHIDManagerClose(mHIDManager, kIOHIDOptionsTypeNone);
+		CFRelease(mHIDManager);
+	}
+
+	void HIDManager::capture(IOHIDDeviceRef device, bool ignoreEvents)
+	{
+		// First trigger any callbacks
+		CFStringRef runLoopMode = getRunLoopMode(mData.type);
+		while(CFRunLoopRunInMode(runLoopMode, 0, TRUE) == kCFRunLoopRunHandledSource)
+		{ /* Do nothing */ }
+
+		for(auto& entry : mData.devices)
+		{
+			if(device != nullptr && entry.ref != device)
+				continue;
+
+			// Read non-queued elements
+			if(!ignoreEvents)
+			{
+				INT32 relX, relY, relZ;
+				relX = relY = relZ = 0;
+
+				struct AxisState
+				{
+					bool moved;
+					INT32 value;
+				};
+
+				AxisState axisValues[24];
+				bs_zero_out(axisValues);
+
+				for (auto& axis : entry.axes)
+				{
+					auto axisType = (InputAxis) -1;
+
+					if (mData.type == HIDType::Gamepad)
+					{
+						INT32 axisValue = HIDGetElementValueScaled(entry, axis, Gamepad::MIN_AXIS, Gamepad::MAX_AXIS);
+						INT32 lastInputAxis = (INT32) InputAxis::RightTrigger + 1;
+						switch (axis.usage)
+						{
+						case kHIDUsage_GD_X:
+							axisType = InputAxis::LeftStickX;
+							break;
+						case kHIDUsage_GD_Y:
+							axisType = InputAxis::LeftStickY;
+							break;
+						case kHIDUsage_GD_Rx:
+							axisType = InputAxis::RightStickX;
+							break;
+						case kHIDUsage_GD_Ry:
+							axisType = InputAxis::RightStickY;
+							break;
+						case kHIDUsage_GD_Z:
+							axisType = InputAxis::LeftTrigger;
+							break;
+						case kHIDUsage_GD_Rz:
+							axisType = InputAxis::RightTrigger;
+							break;
+						case kHIDUsage_GD_Slider:
+							axisType = (InputAxis) (lastInputAxis + 1);
+							break;
+						case kHIDUsage_GD_Dial:
+							axisType = (InputAxis) (lastInputAxis + 2);
+							break;
+						case kHIDUsage_GD_Wheel:
+							axisType = (InputAxis) (lastInputAxis + 3);
+							break;
+						case kHIDUsage_Sim_Rudder:
+							axisType = (InputAxis) (lastInputAxis + 4);
+							break;
+						case kHIDUsage_Sim_Throttle:
+							axisType = (InputAxis) (lastInputAxis + 5);
+							break;
+						case kHIDUsage_Sim_Accelerator:
+							axisType = (InputAxis) (lastInputAxis + 6);
+							break;
+						case kHIDUsage_Sim_Brake:
+							axisType = (InputAxis) (lastInputAxis + 7);
+							break;
+						default:
+							break;
+						}
+
+						if((INT32)axisType < 24)
+						{
+							axisValues[(INT32)axisType].moved = true;
+							axisValues[(INT32)axisType].value = axisValue;
+						}
+
+					}
+					else if (mData.type == HIDType::Mouse)
+					{
+						INT32 axisValue = HIDGetElementValue(entry, axis);
+						switch (axis.usage)
+						{
+						case kHIDUsage_GD_X:
+							axisType = InputAxis::MouseX;
+							relX += axisValue;
+							break;
+						case kHIDUsage_GD_Y:
+							axisType = InputAxis::MouseY;
+							relY += axisValue;
+							break;
+						case kHIDUsage_GD_Z:
+							axisType = InputAxis::MouseZ;
+							relZ += axisValue;
+							break;
+						default:
+							break;
+						}
+					}
+				}
+
+				if(relX != 0 || relY != 0 || relZ != 0)
+					mData.owner->_notifyMouseMoved(relX, relY, relZ);
+
+				for(UINT32 i = 0; i < 24; i++)
+				{
+					if(axisValues[i].moved)
+						mData.owner->_notifyAxisMoved(entry.id, (UINT32)i, axisValues[i].value);
+				}
+			}
+
+			// Read queued elements
+			while(true)
+			{
+				IOHIDValueRef valueRef = IOHIDQueueCopyNextValueWithTimeout(entry.queueRef, 0);
+				if(!valueRef)
+					break;
+
+				if(ignoreEvents)
+					continue;
+
+				IOHIDElementRef elemRef = IOHIDValueGetElement(valueRef);
+				auto value = (INT32) IOHIDValueGetIntegerValue(valueRef); // For buttons this is 1 when pressed, 0 when released
+				UINT64 timestamp = IOHIDValueGetTimeStamp(valueRef);
+
+				UINT32 usage = IOHIDElementGetUsage(elemRef);
+				UINT32 usagePage = IOHIDElementGetUsagePage(elemRef);
+
+				ButtonCode button = BC_UNASSIGNED;
+				if(usagePage == kHIDPage_GenericDesktop)
+				{
+					if (usage == kHIDUsage_GD_Hatswitch)
+					{
+						switch (value)
+						{
+						case 0:
+							button = BC_GAMEPAD_DPAD_UP;
+							break;
+						case 1:
+							button = BC_GAMEPAD_DPAD_UPRIGHT;
+							break;
+						case 2:
+							button = BC_GAMEPAD_DPAD_RIGHT;
+							break;
+						case 3:
+							button = BC_GAMEPAD_DPAD_DOWNRIGHT;
+							break;
+						case 4:
+							button = BC_GAMEPAD_DPAD_DOWN;
+							break;
+						case 5:
+							button = BC_GAMEPAD_DPAD_DOWNLEFT;
+							break;
+						case 6:
+							button = BC_GAMEPAD_DPAD_LEFT;
+							break;
+						case 7:
+							button = BC_GAMEPAD_DPAD_UPLEFT;
+							break;
+						default:
+							break;
+						}
+					}
+				}
+				else if(usagePage == kHIDPage_Button)
+				{
+					if(mData.type == HIDType::Mouse)
+					{
+						if (usage > 0 && usage <= BC_NumMouse)
+							button = (ButtonCode) ((UINT32) BC_MOUSE_LEFT + usage - 1);
+					}
+					else if(mData.type == HIDType::Gamepad)
+					{
+						// These are based on the xbox controller:
+						switch(usage)
+						{
+						case 0: break;
+						case 1: button = BC_GAMEPAD_A; break;
+						case 2: button = BC_GAMEPAD_B; break;
+						case 3: button = BC_GAMEPAD_X; break;
+						case 4: button = BC_GAMEPAD_Y; break;
+						case 5: button = BC_GAMEPAD_LB; break;
+						case 6: button = BC_GAMEPAD_RB; break;
+						case 7: button = BC_GAMEPAD_LS; break;
+						case 8: button = BC_GAMEPAD_RS; break;
+						case 9: button = BC_GAMEPAD_START; break;
+						case 10: button = BC_GAMEPAD_BACK; break;
+						case 11: button = BC_GAMEPAD_BTN1; break;
+						case 12: button = BC_GAMEPAD_DPAD_UP; break;
+						case 13: button = BC_GAMEPAD_DPAD_DOWN; break;
+						case 14: button = BC_GAMEPAD_DPAD_LEFT; break;
+						case 15: button = BC_GAMEPAD_DPAD_RIGHT; break;
+						default:
+						{
+							INT32 buttonIdx = usage - 16;
+							if(buttonIdx < 19)
+								button = (ButtonCode)((INT32)(BC_GAMEPAD_BTN2 + buttonIdx));
+						}
+							break;
+						}
+					}
+				}
+				else if(usagePage == kHIDPage_KeyboardOrKeypad)
+				{
+					// Usage -1 and 1 are special signals that happen along with every button press/release and should be
+					// ignored
+					if(usage != -1 && usage != 1)
+						button = scanCodeToKeyCode((UINT32)value);
+				}
+
+				if(button != BC_UNASSIGNED)
+				{
+					if(value != 0)
+						mData.owner->_notifyButtonPressed(entry.id, button, timestamp);
+					else
+						mData.owner->_notifyButtonReleased(entry.id, button, timestamp);
+				}
+
+				CFRelease(valueRef);
+			}
+		}
+	}
+
+	void Input::initRawInput()
+	{
+		mPlatformData = bs_new<InputPrivateData>();
+
+		mKeyboard = bs_new<Keyboard>("Keyboard", this);
+		mMouse = bs_new<Mouse>("Mouse", this);
+
+		mPlatformData->gamepadHIDManager = bs_new<HIDManager>(HIDType::Gamepad, this);
+
+		for(auto& entry : mPlatformData->gamepadInfos)
+		{
+			entry.hid = mPlatformData->gamepadHIDManager;
+			mGamepads.push_back(bs_new<Gamepad>(entry.name, entry, this));
+		}
+	}
+
+	void Input::cleanUpRawInput()
+	{
+		if (mMouse != nullptr)
+			bs_delete(mMouse);
+
+		if (mKeyboard != nullptr)
+			bs_delete(mKeyboard);
+
+		for (auto& gamepad : mGamepads)
+			bs_delete(gamepad);
+
+		bs_delete(mPlatformData->gamepadHIDManager);
+
+		bs_delete(mPlatformData);
+	}
+
+	UINT32 Input::getDeviceCount(InputDevice device) const
+	{
+		switch(device)
+		{
+		case InputDevice::Keyboard: return 1;
+		case InputDevice::Mouse: return 1;
+		case InputDevice::Gamepad: return (UINT32)mPlatformData->gamepadInfos.size();
+		case InputDevice::Count: return 0;
+		}
+
+		return 0;
+	}
+}
+

+ 104 - 0
Source/BansheeCore/MacOS/BsMacOSInput.h

@@ -0,0 +1,104 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2017 Marko Pintera ([email protected]). All rights reserved. **********************//
+#pragma once
+
+#include "BsCorePrerequisites.h"
+#include <IOKit/hid/IOHIDLib.h>
+
+namespace bs
+{
+	/** Available types of devices supported by the HIDManager. */
+	enum class HIDType
+	{
+		Keyboard,
+		Mouse,
+		Gamepad
+	};
+
+	/**
+	 * Contains information about a single element of an input device (e.g. a button, an axis), as reported by the
+	 * HIDManager.
+	 */
+	struct HIDElement
+	{
+		IOHIDElementRef ref;
+		IOHIDElementCookie cookie;
+
+		INT32 min, max;
+		mutable INT32 detectedMin, detectedMax;
+		UINT32 usage;
+	};
+
+	/** Contains information about a single input device and its elements, as reported by the HIDManager. */
+	struct HIDDevice
+	{
+		IOHIDDeviceRef ref;
+		IOHIDQueueRef queueRef;
+
+		String name;
+		UINT32 id;
+
+		Vector<HIDElement> axes;
+		Vector<HIDElement> buttons;
+		Vector<HIDElement> hats;
+	};
+
+	/** Contains information about all enumerated input devices for a specific HIDManager. */
+	struct HIDData
+	{
+		Vector<HIDDevice> devices;
+		HIDType type;
+		Input* owner = nullptr;
+	};
+
+	/**
+	 * Provides access to the low level IO HID manager. Enumerates available input devices and reports their input to the
+	 * Input object.
+	 */
+	class HIDManager
+	{
+	public:
+		/**
+		 *  Constructs a new HID manager object.
+		 *
+		 * @param type 		Determines what category of input devices will this manager enumerate and report events for.
+		 * @param input		Input object that will by called by the HID manager when input events occur.
+		 */
+		HIDManager(HIDType type, Input* input);
+		~HIDManager();
+
+		/**
+		 * Checks if any new input events have been generates and reports them to the Input object.
+		 *
+		 * @param[in] device		Device to read events from. If null, the events are read from all devices of the
+		 * 							compatible type.
+		 * @param[in] ignoreEvents 	If true the system will not trigger any external events for the reported input. This
+		 * 							can be useful for situations where input is disabled, like an out-of-focus window.
+		 */
+		void capture(IOHIDDeviceRef device, bool ignoreEvents = false);
+
+	private:
+		IOHIDManagerRef mHIDManager = nullptr;
+		HIDData mData;
+	};
+
+	/** Information about a gamepad. */
+	struct GamepadInfo
+	{
+		UINT32 id;
+		String name;
+		IOHIDDeviceRef deviceRef;
+		HIDManager* hid;
+	};
+
+	/**
+	 * Data specific to MacOS implementation of the input system. Can be passed to platform specific implementations of
+	 * the individual device types.
+	 */
+	struct InputPrivateData
+	{
+		Vector<GamepadInfo> gamepadInfos;
+		HIDManager* gamepadHIDManager;
+	};
+}
+

+ 41 - 0
Source/BansheeCore/MacOS/BsMacOSKeyboard.cpp

@@ -0,0 +1,41 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#include "Input/BsKeyboard.h"
+#include "Input/BsInput.h"
+#include "MacOS/BsMacOSInput.h"
+
+namespace bs
+{
+	/** Contains private data for the MacOS Keyboard implementation. */
+	struct Keyboard::Pimpl
+	{
+		explicit Pimpl(Input* owner)
+			:hid(HIDType::Keyboard, owner)
+		{ }
+
+		HIDManager hid;
+		bool hasInputFocus = true;
+	};
+
+	Keyboard::Keyboard(const String& name, Input* owner)
+			: mName(name), mOwner(owner)
+	{
+		m = bs_new<Pimpl>(owner);
+	}
+
+	Keyboard::~Keyboard()
+	{
+		bs_delete(m);
+	}
+
+	void Keyboard::capture()
+	{
+		m->hid.capture(nullptr, !m->hasInputFocus);
+	}
+
+	void Keyboard::changeCaptureContext(UINT64 windowHandle)
+	{
+		m->hasInputFocus = windowHandle != (UINT64)-1;
+	}
+}
+

+ 41 - 0
Source/BansheeCore/MacOS/BsMacOSMouse.cpp

@@ -0,0 +1,41 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#include "Input/BsMouse.h"
+#include "Input/BsInput.h"
+#include "MacOS/BsMacOSInput.h"
+
+namespace bs
+{
+	/** Contains private data for the MacOS Mouse implementation. */
+	struct Mouse::Pimpl
+	{
+		explicit Pimpl(Input* owner)
+			:hid(HIDType::Mouse, owner)
+		{ }
+
+		HIDManager hid;
+		bool hasInputFocus = true;
+	};
+
+	Mouse::Mouse(const String& name, Input* owner)
+			: mName(name), mOwner(owner)
+	{
+		m = bs_new<Pimpl>(owner);
+	}
+
+	Mouse::~Mouse()
+	{
+		bs_delete(m);
+	}
+
+	void Mouse::capture()
+	{
+		m->hid.capture(nullptr, !m->hasInputFocus);
+	}
+
+	void Mouse::changeCaptureContext(UINT64 windowHandle)
+	{
+		m->hasInputFocus = windowHandle != (UINT64)-1;
+	}
+}
+

+ 82 - 0
Source/BansheeCore/MacOS/BsMacOSPlatform.h

@@ -0,0 +1,82 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2017 Marko Pintera ([email protected]). All rights reserved. **********************//
+#pragma once
+
+#include "Platform/BsPlatform.h"
+#import <Cocoa/Cocoa.h>
+
+namespace bs
+{
+	class CocoaWindow;
+
+	/** @addtogroup Platform-Internal
+	 *  @{
+	 */
+
+	/** Contains various macOS specific platform functionality. */
+	class BS_CORE_EXPORT MacOSPlatform : public Platform
+	{
+	public:
+		/** Notifies the system that a new window was created. */
+		static void registerWindow(CocoaWindow* window);
+
+		/** Notifies the system that a window is about to be destroyed. */
+		static void unregisterWindow(CocoaWindow* window);
+
+		/** Generates a Cocoa image from the provided pixel data. */
+		static NSImage* createNSImage(const PixelData& data);
+
+		/** Sends an event notifying the system that a key corresponding to an input command was pressed. */
+		static void sendInputCommandEvent(InputCommandType inputCommand);
+
+		/** Sends an event notifying the system that the user typed some text. */
+		static void sendCharInputEvent(UINT32 character);
+
+		/** Sends an event notifying the system that a pointer button was pressed. */
+		static void sendPointerButtonPressedEvent(
+			const Vector2I& pos,
+			OSMouseButton button,
+			const OSPointerButtonStates& buttonStates);
+
+		/** Sends an event notifying the system that a pointer button was released. */
+		static void sendPointerButtonReleasedEvent(
+			const Vector2I& pos,
+			OSMouseButton button,
+			const OSPointerButtonStates& buttonStates);
+
+		/** Sends an event notifying the system that the user clicked the left pointer button twice in quick succession. */
+		static void sendPointerDoubleClickEvent(const Vector2I& pos, const OSPointerButtonStates& buttonStates);
+
+		/** Sends an event notifying the system that the pointer moved. */
+		static void sendPointerMovedEvent(const Vector2I& pos, const OSPointerButtonStates& buttonStates);
+
+		/** Sends an event notifying the system the user has scrolled the mouse wheel. */
+		static void sendMouseWheelScrollEvent(float delta);
+
+		/** Returns the currently assigned custom cursor. */
+		static NSCursor* _getCurrentCursor();
+
+		/**
+		 * Clips the cursor position to clip bounds, if clipping is enabled. Returns true if clipping occured, and updates
+		 * @p pos to the clipped position.
+		 */
+		static bool _clipCursor(Vector2I& pos);
+
+		/** Updates clip bounds that depend on window size. Should be called after window size changes. */
+		static void _updateClipBounds(NSWindow* window);
+
+		/** Moves the cursor to the specified position in screen coordinates. */
+		static void _setCursorPosition(const Vector2I& position);
+	};
+
+	/** Converts an area in screen space with bottom left origin, to top left origin. */
+	void flipY(NSScreen* screen, NSRect& rect);
+
+	/** Converts a point in screen space with bottom left origin, to top left origin. */
+	void flipY(NSScreen* screen, NSPoint &point);
+
+	/** Converts a point in window space with bottom left origin, to top left origin. */
+	void flipYWindow(NSWindow* window, NSPoint &point);
+
+	/** @} */
+}

+ 912 - 0
Source/BansheeCore/MacOS/BsMacOSPlatform.mm

@@ -0,0 +1,912 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#define BS_COCOA_INTERNALS 1
+#include "MacOS/BsMacOSPlatform.h"
+#include "MacOS/BsMacOSWindow.h"
+#include "Input/BsInputFwd.h"
+#include "Image/BsPixelData.h"
+#include "Image/BsColor.h"
+#include "RenderAPI/BsRenderWindow.h"
+#include "MacOS/BsMacOSDropTarget.h"
+#include "String/BsUnicode.h"
+#include "BsCoreApplication.h"
+#include <atomic>
+#import <Carbon/Carbon.h>
+
+/** Application implementation that overrides the terminate logic with custom shutdown, and tracks Esc key presses. */
+@interface BSApplication : NSApplication
+-(void)terminate:(nullable id)sender;
+-(void)sendEvent:(NSEvent*)event;
+@end
+
+@implementation BSApplication
+-(void)terminate:(nullable id)sender
+{
+	bs::gCoreApplication().quitRequested();
+}
+
+-(void)sendEvent:(NSEvent *)event
+{
+	// Handle Esc key here, as it doesn't seem to be reported elsewhere
+	if([event type] == NSEventTypeKeyDown)
+	{
+		if([event keyCode] == 53) // Escape key
+		{
+			bs::InputCommandType ic = bs::InputCommandType ::Escape;
+			bs::MacOSPlatform::sendInputCommandEvent(ic);
+		}
+	}
+
+	[super sendEvent:event];
+}
+@end
+
+/** Application delegate implementation that activates the application when it finishes launching. */
+@interface BSAppDelegate : NSObject<NSApplicationDelegate>
+@end
+
+@implementation BSAppDelegate : NSObject
+-(void)applicationDidFinishLaunching:(NSNotification *)notification
+{
+	[NSApp activateIgnoringOtherApps:YES];
+}
+@end
+
+@class BSCursor;
+@class BSPlatform;
+
+namespace bs
+{
+	/** Contains information about a currently open modal window. */
+	struct ModalWindowInfo
+	{
+		CocoaWindow* window;
+		NSModalSession session;
+	};
+
+	struct Platform::Pimpl
+	{
+		BSAppDelegate* appDelegate = nil;
+		CocoaWindow* mainWindow = nullptr;
+		Vector<CocoaWindow*> allWindows;
+		Vector<ModalWindowInfo> modalWindows;
+
+		BSPlatform* platformManager = nil;
+
+		// Cursor
+		BSCursor* cursorManager = nil;
+
+		Mutex cursorMutex;
+		bool cursorIsHidden = false;
+		Vector2I cursorPos;
+
+		// Clipboard
+		Mutex clipboardMutex;
+		WString cachedClipboardData;
+		INT32 clipboardChangeCount = -1;
+	};
+
+}
+
+/**
+ * Contains cursor specific functionality. Encapsulated in objective C so its selectors can be triggered from other
+ * threads.
+ */
+@interface BSCursor : NSObject
+@property NSCursor* currentCursor;
+
+-(BSCursor*) initWithPlatformData:(bs::Platform::Pimpl*)platformData;
+-(bs::Vector2I) getPosition;
+-(void) setPosition:(const bs::Vector2I&) position;
+-(BOOL) clipCursor:(bs::Vector2I&) position;
+-(void) updateClipBounds:(NSWindow*) window;
+-(void) clipCursorToWindow:(NSValue*) windowValue;
+-(void) clipCursorToRect:(NSValue*) rectValue;
+-(void) clipCursorDisable;
+-(void) setCursor:(NSArray*) params;
+-(void) unregisterWindow:(NSWindow*) window;
+@end
+
+@implementation BSCursor
+{
+	bs::Platform::Pimpl* platformData;
+
+	bool cursorClipEnabled;
+	bs::Rect2I cursorClipRect;
+	NSWindow* cursorClipWindow;
+}
+
+- (BSCursor*)initWithPlatformData:(bs::Platform::Pimpl*)data
+{
+	self = [super init];
+
+	platformData = data;
+	return self;
+}
+
+- (bs::Vector2I)getPosition
+{
+	NSPoint point = [NSEvent mouseLocation];
+
+	for (NSScreen* screen in [NSScreen screens])
+	{
+		NSRect frame = [screen frame];
+		if (NSMouseInRect(point, frame, NO))
+			bs::flipY(screen, point);
+	}
+
+	bs::Vector2I output;
+	output.x = (int32_t)point.x;
+	output.y = (int32_t)point.y;
+
+	return output;
+}
+
+- (void)setPosition:(const bs::Vector2I&)position
+{
+	NSPoint point = NSMakePoint(position.x, position.y);
+	CGWarpMouseCursorPosition(point);
+
+	Lock lock(platformData->cursorMutex);
+	platformData->cursorPos = position;
+}
+
+- (BOOL)clipCursor:(bs::Vector2I&)position
+{
+	if(!cursorClipEnabled)
+		return false;
+
+	int32_t clippedX = position.x - cursorClipRect.x;
+	int32_t clippedY = position.y - cursorClipRect.y;
+
+	if(clippedX < 0)
+		clippedX = 0;
+	else if(clippedX >= (int32_t)cursorClipRect.width)
+		clippedX = cursorClipRect.width > 0 ? cursorClipRect.width - 1 : 0;
+
+	if(clippedY < 0)
+		clippedY = 0;
+	else if(clippedY >= (int32_t)cursorClipRect.height)
+		clippedY = cursorClipRect.height > 0 ? cursorClipRect.height - 1 : 0;
+
+	clippedX += cursorClipRect.x;
+	clippedY += cursorClipRect.y;
+
+	if(clippedX != position.x || clippedY != position.y)
+	{
+		position.x = clippedX;
+		position.y = clippedY;
+
+		return true;
+	}
+
+	return false;
+}
+
+- (void)updateClipBounds:(NSWindow*)window
+{
+	if(!cursorClipEnabled || cursorClipWindow != window)
+		return;
+
+	NSRect rect = [window contentRectForFrameRect:[window frame]];
+	bs::flipY([window screen], rect);
+
+	cursorClipRect.x = (int32_t)rect.origin.x;
+	cursorClipRect.y = (int32_t)rect.origin.y;
+	cursorClipRect.width = (uint32_t)rect.size.width;
+	cursorClipRect.height = (uint32_t)rect.size.height;
+}
+
+- (void)clipCursorToWindow:(NSValue*)windowValue
+{
+	bs::CocoaWindow* window;
+	[windowValue getValue:&window];
+
+	cursorClipEnabled = true;
+	cursorClipWindow = window->_getPrivateData()->window;
+
+	[self updateClipBounds:cursorClipWindow];
+
+	bs::Vector2I pos = [self getPosition];
+
+	if([self clipCursor:pos])
+		[self setPosition:pos];
+}
+
+- (void)clipCursorToRect:(NSValue*)rectValue
+{
+	bs::Rect2I rect;
+	[rectValue getValue:&rect];
+
+	cursorClipEnabled = true;
+	cursorClipRect = rect;
+	cursorClipWindow = nullptr;
+
+	bs::Vector2I pos = [self getPosition];
+
+	if([self clipCursor:pos])
+		[self setPosition:pos];
+}
+
+- (void)clipCursorDisable
+{
+	cursorClipEnabled = false;
+	cursorClipWindow = nullptr;
+}
+
+- (void)setCursor:(NSArray*)params
+{
+	NSCursor* cursor = params[0];
+	NSValue* hotSpotValue = params[1];
+
+	NSPoint hotSpot;
+	[hotSpotValue getValue:&hotSpot];
+
+	[self setCurrentCursor:cursor];
+
+	for(auto& entry : platformData->allWindows)
+	{
+		NSWindow* window = entry->_getPrivateData()->window;
+		[window
+			performSelectorOnMainThread:@selector(invalidateCursorRectsForView:)
+			withObject:[window contentView]
+			waitUntilDone:NO];
+	}
+}
+
+- (void)unregisterWindow:(NSWindow*)window
+{
+	if(cursorClipEnabled && cursorClipWindow == window)
+		[self clipCursorDisable];
+}
+
+@end
+
+/** Contains platform specific functionality that is meant to be delayed executed from the sim thread, through Platform. */
+@interface BSPlatform : NSObject
+-(BSPlatform*) initWithPlatformData:(bs::Platform::Pimpl*)platformData;
+-(void) setCaptionNonClientAreas:(NSArray*) params;
+-(void) resetNonClientAreas:(NSValue*) windowValue;
+-(void) openFolder:(NSURL*) url;
+-(void) setClipboardText:(NSString*) text;
+-(NSString*) getClipboardText;
+-(int32_t) getClipboardChangeCount;
+@end
+
+@implementation BSPlatform
+{
+	bs::Platform::Pimpl* mPlatformData;
+}
+
+- (BSPlatform*)initWithPlatformData:(bs::Platform::Pimpl*)platformData
+{
+	self = [super init];
+
+	mPlatformData = platformData;
+	return self;
+}
+
+- (void)setCaptionNonClientAreas:(NSArray*)params
+{
+	NSValue* windowValue = params[0];
+	bs::CocoaWindow* window = (bs::CocoaWindow*)[windowValue pointerValue];
+
+	NSUInteger numEntries = [params count] - 1;
+
+	bs::Vector<bs::Rect2I> areas;
+	for(NSUInteger i = 0; i < numEntries; i++)
+	{
+		NSValue* value = params[i];
+
+		bs::Rect2I area;
+		[value getValue:&area];
+
+		areas.push_back(area);
+	}
+
+	window->_setDragZones(areas);
+}
+
+- (void)resetNonClientAreas:(NSValue*) windowValue
+{
+	bs::CocoaWindow* window = (bs::CocoaWindow*)[windowValue pointerValue];
+	window->_setDragZones({});
+}
+
+- (void)openFolder:(NSURL*)url
+{
+	[[NSWorkspace sharedWorkspace] openURL:url];
+}
+
+- (void) setClipboardText:(NSString*) text
+{ @autoreleasepool {
+	NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
+	[pasteboard clearContents];
+	NSArray* objects = [NSArray arrayWithObject:text];
+	[pasteboard writeObjects:objects];
+}}
+
+- (NSString*) getClipboardText
+{ @autoreleasepool {
+	NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
+	NSArray* classes = [NSArray arrayWithObjects:[NSString class], nil];
+	NSDictionary* options = [NSDictionary dictionary];
+
+	NSArray* items = [pasteboard readObjectsForClasses:classes options:options];
+	if(!items)
+		return nil;
+
+	return (NSString*)[items objectAtIndex:0];
+}}
+
+- (int32_t)getClipboardChangeCount
+{
+	return (int32_t)[[NSPasteboard generalPasteboard] changeCount];
+}
+
+@end
+
+namespace bs
+{
+	void flipY(NSScreen* screen, NSRect& rect)
+	{
+		NSRect screenFrame = [screen frame];
+
+		rect.origin.y = screenFrame.size.height - (rect.origin.y + rect.size.height);
+	}
+
+	void flipY(NSScreen* screen, NSPoint &point)
+	{
+		NSRect screenFrame = [screen frame];
+
+		point.y = screenFrame.size.height - point.y;
+	}
+
+	void flipYWindow(NSWindow* window, NSPoint &point)
+	{
+		NSRect windowFrame = [window frame];
+
+		point.y = windowFrame.size.height - point.y;
+	}
+
+	/** Returns the name of the current application based on the information in the app. bundle. */
+	static NSString* getAppName()
+	{
+		NSString* appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
+		if (!appName)
+			appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
+
+		if (![appName length]) {
+			appName = [[NSProcessInfo processInfo] processName];
+		}
+
+		return appName;
+	}
+
+	/** Creates the default menu for the application menu bar. */
+	static void createApplicationMenu()
+	{ @autoreleasepool {
+		NSMenu* mainMenu = [[NSMenu alloc] init];
+		[NSApp setMainMenu:mainMenu];
+
+		NSString* appName = getAppName();
+		NSMenu* appleMenu = [[NSMenu alloc] initWithTitle:@""];
+
+		NSString* aboutTitle = [@"About " stringByAppendingString:appName];
+		[appleMenu addItemWithTitle:aboutTitle
+				   action:@selector(orderFrontStandardAboutPanel:)
+				   keyEquivalent:@""];
+
+		[appleMenu addItem:[NSMenuItem separatorItem]];
+
+		NSString* hideTitle = [@"Hide " stringByAppendingString:appName];
+		[appleMenu addItemWithTitle:hideTitle action:@selector(hide:) keyEquivalent:@"h"];
+
+		NSMenuItem* hideOthersMenuItem = [appleMenu
+			addItemWithTitle:@"Hide Others"
+			action:@selector(hideOtherApplications:)
+			keyEquivalent:@"h"];
+
+		[hideOthersMenuItem setKeyEquivalentModifierMask:(NSEventModifierFlagOption|NSEventModifierFlagCommand)];
+
+		[appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
+
+		[appleMenu addItem:[NSMenuItem separatorItem]];
+
+		NSString* quitTitle = [@"Quit " stringByAppendingString:appName];
+		[appleMenu addItemWithTitle:quitTitle action:@selector(terminate:) keyEquivalent:@"q"];
+
+		NSMenuItem* appleMenuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
+		[appleMenuItem setSubmenu:appleMenu];
+		[[NSApp mainMenu] addItem:appleMenuItem];
+	}}
+
+	Event<void(const Vector2I &, const OSPointerButtonStates &)> Platform::onCursorMoved;
+	Event<void(const Vector2I &, OSMouseButton button, const OSPointerButtonStates &)> Platform::onCursorButtonPressed;
+	Event<void(const Vector2I &, OSMouseButton button, const OSPointerButtonStates &)> Platform::onCursorButtonReleased;
+	Event<void(const Vector2I &, const OSPointerButtonStates &)> Platform::onCursorDoubleClick;
+	Event<void(InputCommandType)> Platform::onInputCommand;
+	Event<void(float)> Platform::onMouseWheelScrolled;
+	Event<void(UINT32)> Platform::onCharInput;
+
+	Event<void()> Platform::onMouseCaptureChanged;
+
+	Platform::Pimpl* Platform::mData = bs_new<Platform::Pimpl>();
+
+	Platform::~Platform()
+	{
+	}
+
+	Vector2I Platform::getCursorPosition()
+	{
+		Lock lock(mData->cursorMutex);
+		return mData->cursorPos;
+	}
+
+	void Platform::setCursorPosition(const Vector2I& screenPos)
+	{
+		NSPoint point = NSMakePoint(screenPos.x, screenPos.y);
+		CGWarpMouseCursorPosition(point);
+
+		Lock lock(mData->cursorMutex);
+		mData->cursorPos = screenPos;
+	}
+
+	void Platform::captureMouse(const RenderWindow& window)
+	{
+		// Do nothing
+	}
+
+	void Platform::releaseMouseCapture()
+	{
+		// Do nothing
+	}
+
+	bool Platform::isPointOverWindow(const RenderWindow& window, const Vector2I& screenPos)
+	{
+		CFArrayRef windowDicts = CGWindowListCopyWindowInfo(
+			kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements,
+			kCGNullWindowID);
+
+		if(!windowDicts)
+			return nil;
+
+		CocoaWindow* cocoaWindow;
+		window.getCustomAttribute("COCOA_WINDOW", &cocoaWindow);
+
+		int32_t requestedWindowNumber = (int32_t)cocoaWindow->_getPrivateData()->windowNumber;
+		CGPoint point = CGPointMake(screenPos.x, screenPos.y);
+
+		CFIndex numEntries = CFArrayGetCount(windowDicts);
+		for(CFIndex i = 0; i < numEntries; i++)
+		{
+			CFDictionaryRef dict = (CFDictionaryRef)CFArrayGetValueAtIndex(windowDicts, i);
+
+			CFNumberRef layerRef = (CFNumberRef) CFDictionaryGetValue(dict, kCGWindowLayer);
+			if(!layerRef)
+				continue;
+
+			// Ignore windows outside of layer 0, as those appear to be desktop elements
+			int32_t layer;
+			CFNumberGetValue(layerRef, kCFNumberIntType, &layer);
+			if(layer != 0)
+				continue;
+
+			CFDictionaryRef boundsRef = (CFDictionaryRef)CFDictionaryGetValue(dict, kCGWindowBounds);
+
+			CGRect rect;
+			CGRectMakeWithDictionaryRepresentation(boundsRef, &rect);
+
+			if(CGRectContainsPoint(rect, point))
+			{
+				// Windows are ordered front to back intrinsically, so the first one we are within bounds of is the one we want
+				CFNumberRef windowNumRef = (CFNumberRef)CFDictionaryGetValue(dict, kCGWindowNumber);
+				int32_t windowNumber;
+				CFNumberGetValue(windowNumRef, kCGWindowIDCFNumberType, &windowNumber);
+
+				return requestedWindowNumber == windowNumber;
+			}
+		}
+
+		return false;
+	}
+
+	void Platform::hideCursor()
+	{
+		Lock lock(mData->cursorMutex);
+
+		if(!mData->cursorIsHidden)
+		{
+			[NSCursor performSelectorOnMainThread:@selector(unhide) withObject:nil waitUntilDone:NO];
+			mData->cursorIsHidden = true;
+		}
+	}
+
+	void Platform::showCursor()
+	{
+		Lock lock(mData->cursorMutex);
+
+		if(mData->cursorIsHidden)
+		{
+			[NSCursor performSelectorOnMainThread:@selector(hide) withObject:nil waitUntilDone:NO];
+			mData->cursorIsHidden = false;
+		}
+	}
+
+	bool Platform::isCursorHidden()
+	{
+		Lock lock(mData->cursorMutex);
+
+		return mData->cursorIsHidden;
+	}
+
+	void Platform::clipCursorToWindow(const RenderWindow& window)
+	{
+		CocoaWindow* cocoaWindow;
+		window.getCustomAttribute("COCOA_WINDOW", &cocoaWindow);
+
+		[mData->cursorManager
+			performSelectorOnMainThread:@selector(clipCursorToWindow:)
+			withObject:[NSValue valueWithPointer:cocoaWindow]
+			waitUntilDone:NO];
+	}
+
+	void Platform::clipCursorToRect(const Rect2I& screenRect)
+	{
+		[mData->cursorManager
+			performSelectorOnMainThread:@selector(clipCursorToRect:)
+			withObject:[NSValue value:&screenRect withObjCType:@encode(Rect2I)]
+			waitUntilDone:NO];
+	}
+
+	void Platform::clipCursorDisable()
+	{
+		[mData->cursorManager
+			performSelectorOnMainThread:@selector(clipCursorDisable)
+			withObject:nil
+			waitUntilDone:NO];
+	}
+
+	void Platform::setCursor(PixelData& pixelData, const Vector2I& hotSpot)
+	{ @autoreleasepool {
+		NSImage* image = MacOSPlatform::createNSImage(pixelData);
+		NSPoint point = NSMakePoint(hotSpot.x, hotSpot.y);
+
+		NSCursor* cursor = [[NSCursor alloc] initWithImage:image hotSpot:point];
+		NSArray* params = @[cursor, [NSValue valueWithPoint:point]];
+
+		[mData->cursorManager
+			performSelectorOnMainThread:@selector(setCursor:) withObject:params waitUntilDone:NO];
+	}}
+
+	void Platform::setIcon(const PixelData& pixelData)
+	{ @autoreleasepool {
+		NSImage* image = MacOSPlatform::createNSImage(pixelData);
+
+		[NSApp performSelectorOnMainThread:@selector(setApplicationIconImage:) withObject:image waitUntilDone:NO];
+	}}
+
+	void Platform::setCaptionNonClientAreas(const ct::RenderWindow& window, const Vector<Rect2I>& nonClientAreas)
+	{ @autoreleasepool {
+		NSMutableArray* params = [[NSMutableArray alloc] init];
+
+		CocoaWindow* cocoaWindow;
+		window.getCustomAttribute("COCOA_WINDOW", &cocoaWindow);
+
+		[params addObject:[NSValue valueWithPointer:cocoaWindow]];
+		for(auto& entry : nonClientAreas)
+			[params addObject:[NSValue value:&entry withObjCType:@encode(bs::Rect2I)]];
+
+		[mData->platformManager
+			performSelectorOnMainThread:@selector(setCaptionNonClientAreas:)
+			withObject:params
+			waitUntilDone:NO];
+	}}
+
+	void Platform::setResizeNonClientAreas(const ct::RenderWindow& window, const Vector<NonClientResizeArea>& nonClientAreas)
+	{
+		// Do nothing, custom resize areas not needed on MacOS
+	}
+
+	void Platform::resetNonClientAreas(const ct::RenderWindow& window)
+	{
+		CocoaWindow* cocoaWindow;
+		window.getCustomAttribute("COCOA_WINDOW", &cocoaWindow);
+
+		NSValue* windowValue = [NSValue valueWithPointer:cocoaWindow];
+
+		[mData->platformManager
+			performSelectorOnMainThread:@selector(resetNonClientAreas:)
+			withObject:windowValue
+			waitUntilDone:NO];
+	}
+
+	void Platform::sleep(UINT32 duration)
+	{
+		usleep(duration * 1000);
+	}
+
+	void Platform::copyToClipboard(const WString& string)
+	{ @autoreleasepool {
+		String utf8String = UTF8::fromWide(string);
+
+		NSString* text = [NSString stringWithUTF8String:utf8String.c_str()];
+		[mData->platformManager performSelectorOnMainThread:@selector(setClipboardText:)
+			withObject:text
+			waitUntilDone:NO];
+	}}
+
+	WString Platform::copyFromClipboard()
+	{
+		Lock lock(mData->clipboardMutex);
+		return mData->cachedClipboardData;
+	}
+
+	WString Platform::keyCodeToUnicode(UINT32 keyCode)
+	{
+		TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
+		CFDataRef layoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData);
+		const UCKeyboardLayout* keyLayout = (const UCKeyboardLayout*)CFDataGetBytePtr(layoutData);
+
+		UINT32 keysDown = 0;
+		UniChar chars[4];
+		UniCharCount length = 0;
+
+		UCKeyTranslate(
+			keyLayout,
+			(unsigned short)keyCode,
+			kUCKeyActionDisplay,
+			0,
+			LMGetKbdType(),
+			kUCKeyTranslateNoDeadKeysBit,
+			&keysDown,
+			sizeof(chars) / sizeof(chars[0]),
+			&length,
+			chars);
+
+		CFRelease(keyLayout);
+
+		U16String u16String((char16_t*)chars, (size_t)length);
+		String utf8String = UTF8::fromUTF16(u16String);
+
+		return UTF8::toWide(utf8String);
+	}
+
+	void Platform::openFolder(const Path& path)
+	{
+		String pathStr = path.toString();
+
+		NSURL* url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:pathStr.c_str()]];
+		[mData->platformManager
+			performSelectorOnMainThread:@selector(openFolder:)
+			withObject:url
+			waitUntilDone:NO];
+	}
+
+	void Platform::_startUp()
+	{
+		mData->appDelegate = [[BSAppDelegate alloc] init];
+		mData->cursorManager = [[BSCursor alloc] initWithPlatformData:mData];
+		mData->platformManager = [[BSPlatform alloc] initWithPlatformData:mData];
+		[BSApplication sharedApplication];
+
+		[NSApp setDelegate:mData->appDelegate];
+		[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
+
+		createApplicationMenu();
+
+		[NSApp finishLaunching];
+	}
+
+	void Platform::_update()
+	{
+		CocoaDragAndDrop::update();
+	}
+
+	void Platform::_coreUpdate()
+	{
+		{
+			Lock lock(mData->cursorMutex);
+			mData->cursorPos = [mData->cursorManager getPosition];
+		}
+
+		CocoaDragAndDrop::coreUpdate();
+
+		INT32 changeCount = [mData->platformManager getClipboardChangeCount];
+		if(mData->clipboardChangeCount != changeCount)
+		{
+			NSString* string = [mData->platformManager getClipboardText];
+			String utf8String = [string UTF8String];
+
+			{
+				Lock lock(mData->clipboardMutex);
+				mData->cachedClipboardData = UTF8::toWide(utf8String);
+			}
+
+			mData->clipboardChangeCount = changeCount;
+		}
+
+		_messagePump();
+	}
+
+	void Platform::_shutDown()
+	{
+		// Do nothing
+	}
+
+	void Platform::_messagePump()
+	{ @autoreleasepool {
+		while(true)
+		{
+			if(!mData->modalWindows.empty())
+			{
+				if([NSApp runModalSession:mData->modalWindows.back().session] != NSModalResponseContinue)
+					break;
+			}
+			else
+			{
+				NSEvent* event = [NSApp
+					nextEventMatchingMask:NSEventMaskAny
+					untilDate:[NSDate distantPast]
+					inMode:NSDefaultRunLoopMode
+					dequeue:YES];
+
+				if (!event)
+					break;
+
+				[NSApp sendEvent:event];
+			}
+		}
+	}}
+
+	void MacOSPlatform::registerWindow(CocoaWindow* window)
+	{
+		// First window is assumed to be main
+		if(!mData->mainWindow)
+			mData->mainWindow = window;
+
+		mData->allWindows.push_back(window);
+
+		CocoaWindow::Pimpl* windowData = window->_getPrivateData();
+		if(windowData->isModal)
+		{
+			ModalWindowInfo info;
+			info.window = window;
+			info.session = [NSApp beginModalSessionForWindow:windowData->window];
+
+			mData->modalWindows.push_back(info);
+		}
+	}
+
+	void MacOSPlatform::unregisterWindow(CocoaWindow* window)
+	{
+		CocoaWindow::Pimpl* windowData = window->_getPrivateData();
+		if(windowData->isModal)
+		{
+			auto findIter = std::find_if(mData->modalWindows.begin(), mData->modalWindows.begin(),
+				[window](const ModalWindowInfo& x)
+				{
+					return x.window == window;
+				});
+
+			if(findIter != mData->modalWindows.end())
+			{
+				[NSApp endModalSession:findIter->session];
+				mData->modalWindows.erase(findIter);
+			}
+		}
+
+		auto findIter = std::find(mData->allWindows.begin(), mData->allWindows.end(), window);
+		if(findIter != mData->allWindows.end())
+			mData->allWindows.erase(findIter);
+
+		[mData->cursorManager unregisterWindow:windowData->window];
+
+		// Shut down app when the main window is closed
+		if(mData->mainWindow == window)
+		{
+			bs::gCoreApplication().quitRequested();
+			mData->mainWindow = nullptr;
+		}
+	}
+
+	NSImage* MacOSPlatform::createNSImage(const PixelData& data)
+	{
+		// Premultiply alpha
+		Vector<Color> colors = data.getColors();
+		for(auto& color : colors)
+		{
+			color.r *= color.a;
+			color.g *= color.a;
+			color.b *= color.a;
+		}
+
+		// Convert to RGBA
+		SPtr<PixelData> rgbaData = PixelData::create(data.getWidth(), data.getHeight(), 1, PF_RGBA8);
+		rgbaData->setColors(colors);
+		@autoreleasepool
+		{
+			INT32 pitch = data.getWidth() * sizeof(UINT32);
+			NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc]
+										  initWithBitmapDataPlanes:nullptr
+										  pixelsWide:data.getWidth()
+										  pixelsHigh:data.getHeight()
+										  bitsPerSample:8
+										  samplesPerPixel:4
+										  hasAlpha:YES
+										  isPlanar:NO
+										  colorSpaceName:NSDeviceRGBColorSpace
+										  bytesPerRow:pitch
+										  bitsPerPixel:32];
+
+			unsigned char* pixels = [imageRep bitmapData];
+			memcpy(pixels, rgbaData->getData(), data.getHeight() * pitch);
+
+			NSImage* image = [[NSImage alloc] initWithSize:NSMakeSize(data.getWidth(), data.getHeight())];
+			[image addRepresentation:imageRep];
+
+			return image;
+		}
+	}
+
+	void MacOSPlatform::sendInputCommandEvent(InputCommandType inputCommand)
+	{
+		onInputCommand(inputCommand);
+	}
+
+	void MacOSPlatform::sendCharInputEvent(UINT32 character)
+	{
+		onCharInput(character);
+	}
+
+	void MacOSPlatform::sendPointerButtonPressedEvent(
+		const Vector2I& pos,
+		OSMouseButton button,
+		const OSPointerButtonStates& buttonStates)
+	{
+		onCursorButtonPressed(pos, button, buttonStates);
+	}
+
+	void MacOSPlatform::sendPointerButtonReleasedEvent(
+		const Vector2I& pos,
+		OSMouseButton button,
+		const OSPointerButtonStates& buttonStates)
+	{
+		onCursorButtonReleased(pos, button, buttonStates);
+	}
+
+	void MacOSPlatform::sendPointerDoubleClickEvent(const Vector2I& pos, const OSPointerButtonStates& buttonStates)
+	{
+		onCursorDoubleClick(pos, buttonStates);
+	}
+
+	void MacOSPlatform::sendPointerMovedEvent(const Vector2I& pos, const OSPointerButtonStates& buttonStates)
+	{
+		onCursorMoved(pos, buttonStates);
+	}
+
+	void MacOSPlatform::sendMouseWheelScrollEvent(float delta)
+	{
+		onMouseWheelScrolled(delta);
+	}
+
+	NSCursor* MacOSPlatform::_getCurrentCursor()
+	{
+		return [mData->cursorManager currentCursor];
+	}
+
+	bool MacOSPlatform::_clipCursor(Vector2I& pos)
+	{
+		return [mData->cursorManager clipCursor:pos];
+	}
+
+	void MacOSPlatform::_updateClipBounds(NSWindow* window)
+	{
+		[mData->cursorManager updateClipBounds:window];
+	}
+
+	void MacOSPlatform::_setCursorPosition(const Vector2I& position)
+	{
+		[mData->cursorManager setPosition:position];
+	}
+}

+ 158 - 0
Source/BansheeCore/MacOS/BsMacOSWindow.h

@@ -0,0 +1,158 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2017 Marko Pintera ([email protected]). All rights reserved. **********************//
+#pragma once
+
+#include "Prerequisites/BsPrerequisitesUtil.h"
+#include "Math/BsVector2I.h"
+
+#ifdef BS_COCOA_INTERNALS
+#import <Cocoa/Cocoa.h>
+#endif
+
+#ifdef BS_COCOA_INTERNALS
+@class BSWindowDelegate;
+@class BSWindowListener;
+@class BSView;
+#endif
+
+namespace bs
+{
+	class CocoaDropTarget;
+
+	/** @addtogroup Internal-Utility
+	 *  @{
+	 */
+
+	/** @addtogroup Platform-Utility-Internal
+	 *  @{
+	 */
+
+	/**	Descriptor used for creating a platform specific native window. */
+	struct WINDOW_DESC
+	{
+		String title;
+		INT32 x = -1;
+		INT32 y = -1;
+		UINT32 width = 20;
+		UINT32 height = 20;
+		bool showDecorations = true;
+		bool allowResize = true;
+		bool modal = false;
+		SPtr<PixelData> background;
+	};
+
+	/** Represents a Cocoa window. */
+	class BS_UTILITY_EXPORT CocoaWindow
+	{
+	public:
+#ifdef BS_COCOA_INTERNALS
+		struct Pimpl
+		{
+			NSWindow* window;
+			BSView* view;
+			BSWindowListener* responder;
+			BSWindowDelegate* delegate;
+
+			NSUInteger style = 0;
+			NSInteger windowNumber = 0;
+			bool isFullscreen = false;
+			NSRect windowedRect;
+			bool isModal = false;
+			UINT32 numDropTargets = 0;
+			void* userData = nullptr;
+		};
+#else
+		struct Pimpl;
+#endif
+
+		CocoaWindow(const WINDOW_DESC& desc);
+		~CocoaWindow();
+
+		/**	Returns the current area of the window, relative to the screen. */
+		Rect2I getArea() const;
+
+		/** Hides the window. */
+		void hide();
+
+		/** Shows (unhides) the window. */
+		void show();
+
+		/**	Minimizes the window. */
+		void minimize();
+
+		/**	Maximizes the window over the entire current screen. */
+		void maximize();
+
+		/**	Restores the window to original position and size if it is minimized or maximized. */
+		void restore();
+
+		/**	Change the size of the window. */
+		void resize(UINT32 width, UINT32 height);
+
+		/**	Reposition the window. */
+		void move(INT32 left, INT32 top);
+
+		/** Switches from fullscreen to windowed mode. */
+		void setWindowed();
+
+		/** Switches from windowed to fullscreen mode. */
+		void setFullscreen();
+
+		/**	Converts screen position into window local position. */
+		Vector2I screenToWindowPos(const Vector2I& screenPos) const;
+
+		/**	Converts window local position to screen position. */
+		Vector2I windowToScreenPos(const Vector2I& windowPos) const;
+
+		/**
+		 * @name Internal
+		 * @{
+		 */
+
+		/**
+		 * Destroys the window, cleaning up any resources and removing it from the display. No further methods should be
+		 * called on this object after it has been destroyed.
+		 */
+		void _destroy();
+
+		/**
+		 * Sets a portion of the window in which the user can click and drag in order to move the window. This is needed
+		 * when window has no title bar, yet you still want to allow the user to drag it by clicking on some specific area
+		 * (e.g. a title bar you manually render).
+		 *
+		 * @param[in]	rects	Areas of the window (relative to the window origin in top-left corner) in which the drag
+		 * 						operation in allowed.
+		 */
+		void _setDragZones(const Vector<Rect2I>& rects);
+
+		/** Attaches non-specific user data that can later be retrieved through _getUserData(). */
+		void _setUserData(void* data);
+
+		/** Returns user data attached to the object when _setUserData was called. */
+		void* _getUserData() const;
+
+		/**
+		 * Registers the window with the drag and drop manager and allows it to accept file drop operations. Each call
+		 * to this method must eventually be followed with _unregisterForDragAndDrop.
+		 */
+		void _registerForDragAndDrop();
+
+		/**
+		 * Unregisters the window from the drag and drop manager. This will need to be called multiple times if
+		 * _registerForDragAndDrop was called multiple times.
+		 */
+		void _unregisterForDragAndDrop();
+
+		/** Returns internal private data for use by friends. */
+		Pimpl* _getPrivateData() const { return m; }
+
+		/** @} */
+
+	private:
+		Pimpl* m;
+	};
+
+	/** @} */
+	/** @} */
+}
+

+ 812 - 0
Source/BansheeCore/MacOS/BsMacOSWindow.mm

@@ -0,0 +1,812 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2017 Marko Pintera ([email protected]). All rights reserved. **********************//
+#define BS_COCOA_INTERNALS 1
+#include "MacOS/BsMacOSWindow.h"
+#include "MacOS/BsMacOSPlatform.h"
+#include "MacOS/BsMacOSDropTarget.h"
+#include "Math/BsRect2I.h"
+#include "Input/BsInputFwd.h"
+#include "String/BsUnicode.h"
+#include "RenderAPI/BsRenderWindow.h"
+
+static bool keyCodeToInputCommand(uint32_t keyCode, bool shift, bs::InputCommandType& inputCommand)
+{
+	switch(keyCode)
+	{
+		case 36: // Return
+			inputCommand = shift ? bs::InputCommandType::Return : bs::InputCommandType::Confirm;
+			return true;
+		case 51: // Backspace
+			inputCommand = bs::InputCommandType::Backspace;
+			return true;
+		case 53: // Escape
+			inputCommand = bs::InputCommandType::Escape;
+			return true;
+		case 117: // Delete
+			inputCommand = bs::InputCommandType::Delete;
+			return true;
+		case 123: // Left
+			inputCommand = shift ? bs::InputCommandType::SelectLeft : bs::InputCommandType::CursorMoveLeft;
+			return true;
+		case 124: // Right
+			inputCommand = shift ? bs::InputCommandType::SelectRight : bs::InputCommandType::CursorMoveRight;
+			return true;
+		case 125: // Down
+			inputCommand = shift ? bs::InputCommandType::SelectDown : bs::InputCommandType::CursorMoveDown;
+			return true;
+		case 126: // Up
+			inputCommand = shift ? bs::InputCommandType::SelectUp : bs::InputCommandType::CursorMoveUp;
+			return true;
+	}
+
+	return false;
+}
+
+/**
+ * Overrides window so even borderless windows can become key windows (allowing resize events and cursor changes, among
+ * others.
+ */
+@interface BSWindow : NSWindow
+@end
+
+@implementation BSWindow
+- (BOOL)canBecomeKeyWindow
+{
+	return YES;
+}
+@end
+
+/** Implementation of NSView that handles custom cursors, transparent background images and reports the right mouse click. */
+@interface BSView : NSView
+@property (atomic, strong) NSArray* resizeAreas;
+
+-(void)rightMouseDown:(NSEvent *) event;
+-(void)setBackgroundImage:(NSImage*)image;
+@end
+
+@implementation BSView
+{
+	NSTrackingArea* mTrackingArea;
+	NSImageView* mImageView;
+}
+
+-(id)init
+{
+	self = [super init];
+
+	mTrackingArea = nil;
+	mImageView = nil;
+
+	return self;
+}
+
+-(void)resetCursorRects
+{
+	[super resetCursorRects];
+
+	[self addCursorRect:[self bounds] cursor:bs::MacOSPlatform::_getCurrentCursor()];
+}
+
+-(void)updateTrackingAreas
+{
+	[super updateTrackingAreas];
+
+	if(mTrackingArea)
+		[self removeTrackingArea:mTrackingArea];
+
+	NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingActiveInActiveApp;
+	mTrackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:[self window] userInfo:nil];
+
+	[self addTrackingArea:mTrackingArea];
+}
+
+-(void)rightMouseDown:(NSEvent*)event
+{
+	// By default the view eats the right mouse event, but we instead forward the event to window's responder for normal
+	// handling
+	if([event.window nextResponder])
+		[[event.window nextResponder] rightMouseDown:event];
+}
+
+-(void)setBackgroundImage:(NSImage*)image
+{ @autoreleasepool {
+	if(image)
+	{
+		NSRect frame = [self frame];
+		frame.origin = NSMakePoint(0, 0);
+
+		mImageView = [[NSImageView alloc] initWithFrame:frame];
+		[mImageView setImageScaling:NSImageScaleAxesIndependently];
+		[mImageView setImage:image];
+		[mImageView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+
+		self.subviews = @[mImageView];
+	}
+	else
+		self.subviews = @[];
+}}
+@end
+
+/** Types of mouse events reported by BSWindowListener. */
+enum class MouseEventType
+{
+	ButtonUp, ButtonDown
+};
+
+/** Listens to mouse and keyboard events for a specific window. Reports them to Platform accordingly. */
+@interface BSWindowListener : NSResponder
+
+// Properties
+@property (atomic, strong) NSArray* dragAreas;
+
+// Mouse
+-(void) handleMouseEvent:(NSEvent *) event type:(MouseEventType) type button:(bs::OSMouseButton) button;
+-(void) mouseDown:(NSEvent *) event;
+-(void) rightMouseDown:(NSEvent *) event;
+-(void) otherMouseDown:(NSEvent *) event;
+-(void) mouseUp:(NSEvent *) event;
+-(void) rightMouseUp:(NSEvent *) event;
+-(void) otherMouseUp:(NSEvent *) event;
+-(void) mouseMoved:(NSEvent *) event;
+-(void) mouseDragged:(NSEvent *) event;
+-(void) rightMouseDragged:(NSEvent *) event;
+-(void) otherMouseDragged:(NSEvent *) event;
+-(void) scrollWheel:(NSEvent *) event;
+
+// Keyboard
+-(void) keyDown:(NSEvent *)event;
+
+// Other
+-(BSWindowListener*)initWithOwner:(bs::CocoaWindow*) owner;
+@end
+
+@implementation BSWindowListener
+{
+	bs::CocoaWindow* mOwner;
+}
+@synthesize dragAreas;
+
+- (BSWindowListener* )initWithOwner:(bs::CocoaWindow*)owner
+{
+	self = [super init];
+
+	mOwner = owner;
+	dragAreas = nil;
+
+	return self;
+}
+
+- (void)handleMouseEvent:(NSEvent *)event type:(MouseEventType) type button:(bs::OSMouseButton) button
+{
+	NSPoint screenPos = NSEvent.mouseLocation;
+	NSUInteger modifierFlags = NSEvent.modifierFlags;
+	uint32_t pressedButtons = (uint32_t)NSEvent.pressedMouseButtons;
+
+	bs::OSPointerButtonStates buttonStates;
+	buttonStates.ctrl = (modifierFlags & NSEventModifierFlagControl) != 0;
+	buttonStates.shift = (modifierFlags & NSEventModifierFlagShift) != 0;
+	buttonStates.mouseButtons[0] = (pressedButtons & (1 << 0)) != 0;
+	buttonStates.mouseButtons[1] = (pressedButtons & (1 << 1)) != 0;
+	buttonStates.mouseButtons[2] = (pressedButtons & (1 << 2)) != 0;
+
+	NSWindow* window = [event window];
+	NSScreen* screen = window ? [window screen] : [NSScreen mainScreen];
+
+	bs::flipY(screen, screenPos);
+	bs::Vector2I pos((int32_t)screenPos.x, (int32_t)screenPos.y);
+
+	if(type == MouseEventType::ButtonDown)
+		bs::MacOSPlatform::sendPointerButtonPressedEvent(pos, button, buttonStates);
+	else // ButtonUp
+	{
+		if([event clickCount] == 2 && button == bs::OSMouseButton::Left)
+			bs::MacOSPlatform::sendPointerDoubleClickEvent(pos, buttonStates);
+		else
+			bs::MacOSPlatform::sendPointerButtonReleasedEvent(pos, button, buttonStates);
+	}
+}
+
+- (void)mouseDown:(NSEvent *)event
+{
+	// Check for manual drag
+	bool isManualDrag = false;
+	if(dragAreas)
+	{
+		NSPoint point = [event locationInWindow];
+		NSWindow* window = [event window];
+		NSScreen* screen = nil;
+		if(window)
+		{
+			NSRect windowFrame = [window frame];
+			point.x += windowFrame.origin.x;
+			point.y += windowFrame.origin.y;
+
+			screen = [window screen];
+		}
+		else
+			screen = NSScreen.mainScreen;
+
+		bs::flipY(screen, point);
+
+		for (NSUInteger i = 0; i < [dragAreas count]; i++)
+		{
+			bs::Rect2I rect;
+			[dragAreas[i] getValue:&rect];
+
+			if(point.x >= rect.x && point.x < (rect.x + rect.width) &&
+					(point.y >= rect.y && point.y < (rect.y + rect.height)))
+			{
+				[window performWindowDragWithEvent:event];
+				isManualDrag = true;
+				break;
+			}
+		}
+	}
+
+	if(!isManualDrag)
+		[self handleMouseEvent:event type:MouseEventType::ButtonDown button:bs::OSMouseButton::Left];
+}
+
+- (void)rightMouseDown:(NSEvent *)event
+{
+	[self handleMouseEvent:event type:MouseEventType::ButtonDown button:bs::OSMouseButton::Right];
+}
+
+- (void)otherMouseDown:(NSEvent *)event
+{
+	[self handleMouseEvent:event type:MouseEventType::ButtonDown button:bs::OSMouseButton::Middle];
+}
+
+- (void)mouseUp:(NSEvent *)event
+{
+	[self handleMouseEvent:event type:MouseEventType::ButtonUp button:bs::OSMouseButton::Left];
+}
+
+- (void)rightMouseUp:(NSEvent *)event
+{
+	[self handleMouseEvent:event type:MouseEventType::ButtonUp button:bs::OSMouseButton::Right];
+}
+
+- (void)otherMouseUp:(NSEvent *)event
+{
+	[self handleMouseEvent:event type:MouseEventType::ButtonUp button:bs::OSMouseButton::Middle];
+}
+
+- (void)mouseMoved:(NSEvent *)event
+{
+	uint32_t pressedButtons = (uint32_t)NSEvent.pressedMouseButtons;
+
+	NSPoint point = [event locationInWindow];
+	NSWindow* window = [event window];
+	NSScreen* screen = nil;
+	if(window)
+	{
+		NSRect windowFrame = [window frame];
+		point.x += windowFrame.origin.x;
+		point.y += windowFrame.origin.y;
+
+		screen = [window screen];
+	}
+	else
+		screen = NSScreen.mainScreen;
+
+	bs::flipY(screen, point);
+
+	bs::Vector2I pos;
+	pos.x = (int32_t)point.x;
+	pos.y = (int32_t)point.y;
+
+	if(bs::MacOSPlatform::_clipCursor(pos))
+		bs::MacOSPlatform::_setCursorPosition(pos);
+
+	NSUInteger modifierFlags = NSEvent.modifierFlags;
+
+	bs::OSPointerButtonStates buttonStates;
+	buttonStates.ctrl = (modifierFlags & NSEventModifierFlagControl) != 0;
+	buttonStates.shift = (modifierFlags & NSEventModifierFlagShift) != 0;
+	buttonStates.mouseButtons[0] = (pressedButtons & (1 << 0)) != 0;
+	buttonStates.mouseButtons[1] = (pressedButtons & (1 << 1)) != 0;
+	buttonStates.mouseButtons[2] = (pressedButtons & (1 << 2)) != 0;
+
+	bs::MacOSPlatform::sendPointerMovedEvent(pos, buttonStates);
+}
+
+- (void)mouseDragged:(NSEvent *)event
+{
+	[self mouseMoved:event];
+}
+
+- (void)rightMouseDragged:(NSEvent *)event
+{
+	[self mouseMoved:event];
+}
+
+- (void)otherMouseDragged:(NSEvent *)event
+{
+	[self mouseMoved:event];
+}
+
+- (void)scrollWheel:(NSEvent *)event
+{
+	float y = (float)[event deltaY];
+
+	bs::MacOSPlatform::sendMouseWheelScrollEvent((float)y);
+}
+
+- (void)keyDown:(NSEvent *)event
+{
+	NSString* string = event.characters;
+	uint32_t keyCode = event.keyCode;
+
+	NSUInteger modifierFlags = NSEvent.modifierFlags;
+	bool shift = (modifierFlags & NSEventModifierFlagShift) != 0;
+
+	bs::InputCommandType ict;
+	if(keyCodeToInputCommand(keyCode, shift, ict))
+		bs::MacOSPlatform::sendInputCommandEvent(ict);
+	else
+	{
+		bs::String utf8String = [string UTF8String];
+		bs::U32String utf32String = bs::UTF8::toUTF32(utf8String);
+
+		for(size_t i = 0; utf32String.length(); i++)
+			bs::MacOSPlatform::sendCharInputEvent(utf32String[i]);
+	}
+}
+
+- (void)mouseEntered:(NSEvent*)event
+{
+	// Do nothing
+}
+
+- (void)mouseExited:(NSEvent*)event
+{
+	bs::ct::RenderWindow* renderWindow = (bs::ct::RenderWindow*)mOwner->_getUserData();
+	if(renderWindow != nullptr)
+		renderWindow->_notifyMouseLeft();
+}
+@end
+
+/** Converts a point from coordinates relative to window's frame, into coordinates relative to window's content rectangle. */
+NSPoint frameToContentRect(NSWindow* window, NSPoint framePoint)
+{
+	bs::flipYWindow(window, framePoint);
+
+	NSRect frameRect = [window frame];
+	NSRect contentRect = [window contentRectForFrameRect:frameRect];
+
+	NSPoint offset;
+	offset.x = frameRect.origin.x - contentRect.origin.x;
+	offset.y = (frameRect.origin.y + frameRect.size.height) - (contentRect.origin.y + contentRect.size.height);
+
+	framePoint.x -= offset.x;
+	framePoint.y -= offset.y;
+
+	return framePoint;
+}
+
+/** Listens to window move, resize, focus change and close events, handles drag and drop operations. */
+@interface BSWindowDelegate : NSObject<NSWindowDelegate, NSDraggingDestination>
+-(id)initWithWindow:(bs::CocoaWindow*)window;
+@end
+
+@implementation BSWindowDelegate
+{
+	bs::CocoaWindow* mWindow;
+	NSRect mStandardZoomFrame;
+	bool mIsZooming;
+}
+
+- (id)initWithWindow:(bs::CocoaWindow*)window
+{
+	self = [super init];
+
+	mWindow = window;
+	mIsZooming = false;
+	return self;
+}
+
+- (void)windowWillClose:(NSNotification *)notification
+{
+	// If it's a render window we allow the client code to handle the message
+	bs::ct::RenderWindow* renderWindow = (bs::ct::RenderWindow*)mWindow->_getUserData();
+	if(renderWindow != nullptr)
+		renderWindow->_notifyCloseRequested();
+	else // If not, we just destroy the window
+		mWindow->_destroy();
+}
+
+- (void)windowDidBecomeKey:(NSNotification*)notification
+{
+	bs::ct::RenderWindow* renderWindow = (bs::ct::RenderWindow*)mWindow->_getUserData();
+	if(renderWindow != nullptr)
+		renderWindow->_windowFocusReceived();
+}
+
+- (void)windowDidResignKey:(NSNotification*)notification
+{
+	bs::ct::RenderWindow* renderWindow = (bs::ct::RenderWindow*)mWindow->_getUserData();
+	if(renderWindow != nullptr)
+		renderWindow->_windowFocusLost();
+}
+
+- (void)windowDidResize:(NSNotification*)notification
+{
+	if([[notification object] isKindOfClass:[NSWindow class]])
+		bs::MacOSPlatform::_updateClipBounds([notification object]);
+
+	bs::ct::RenderWindow* renderWindow = (bs::ct::RenderWindow*)mWindow->_getUserData();
+	if(renderWindow != nullptr)
+		renderWindow->_windowMovedOrResized();
+}
+
+- (void)windowDidMove:(NSNotification*)notification
+{
+	bs::ct::RenderWindow* renderWindow = (bs::ct::RenderWindow*)mWindow->_getUserData();
+	if(renderWindow != nullptr)
+		renderWindow->_windowMovedOrResized();
+}
+
+- (void)windowDidMiniaturize:(NSNotification*)notification
+{
+	bs::ct::RenderWindow* renderWindow = (bs::ct::RenderWindow*)mWindow->_getUserData();
+	if(renderWindow != nullptr)
+		renderWindow->_notifyMinimized();
+}
+
+- (void)windowDidDeminiaturize:(NSNotification*)notification
+{
+	bs::ct::RenderWindow* renderWindow = (bs::ct::RenderWindow*)mWindow->_getUserData();
+	if(renderWindow != nullptr)
+		renderWindow->_notifyRestored();
+}
+
+- (BOOL)windowShouldZoom:(NSWindow*)window toFrame:(NSRect)newFrame
+{
+	// Maximizing, or restoring
+	if(mIsZooming)
+	{
+		bs::ct::RenderWindow* renderWindow = (bs::ct::RenderWindow*)mWindow->_getUserData();
+		if(renderWindow != nullptr)
+		{
+			if (newFrame.size.width == mStandardZoomFrame.size.width &&
+				newFrame.size.height == mStandardZoomFrame.size.height)
+				renderWindow->_notifyMaximized();
+			else
+				renderWindow->_notifyRestored();
+		}
+
+		mIsZooming = true;
+	}
+
+	return YES;
+}
+
+- (NSRect)windowWillUseStandardFrame:(NSWindow*)window defaultFrame:(NSRect)newFrame
+{
+	mIsZooming = true;
+	mStandardZoomFrame = newFrame;
+
+	return newFrame;
+}
+
+- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
+{
+	bs::CocoaWindow::Pimpl* privateData = mWindow->_getPrivateData();
+
+	NSPoint point = [sender draggingLocation];
+	point = frameToContentRect(privateData->window, point);
+
+	bs::Vector2I position((int32_t)point.x, (int32_t)point.y);
+	if(bs::CocoaDragAndDrop::_notifyDragEntered(mWindow, position))
+		return NSDragOperationLink;
+
+	return NSDragOperationNone;
+}
+
+- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
+{
+	bs::CocoaWindow::Pimpl* privateData = mWindow->_getPrivateData();
+
+	NSPoint point = [sender draggingLocation];
+	point = frameToContentRect(privateData->window, point);
+
+	bs::Vector2I position((int32_t)point.x, (int32_t)point.y);
+	if(bs::CocoaDragAndDrop::_notifyDragMoved(mWindow, position))
+		return NSDragOperationLink;
+
+	return NSDragOperationNone;
+}
+
+- (void)draggingExited:(nullable id <NSDraggingInfo>)sender
+{
+	bs::CocoaDragAndDrop::_notifyDragLeft(mWindow);
+}
+
+- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
+{
+	NSPasteboard* pasteboard = [sender draggingPasteboard];
+	if([[pasteboard types] containsObject:NSFilenamesPboardType])
+	{
+		NSArray* entries = [pasteboard propertyListForType:NSFilenamesPboardType];
+
+		bs::Vector<bs::Path> paths;
+		for(NSString* path in entries)
+		{
+			const char* pathChars = [path UTF8String];
+			paths.push_back(bs::Path(pathChars));
+		}
+
+		bs::CocoaWindow::Pimpl* privateData = mWindow->_getPrivateData();
+
+		NSPoint point = [sender draggingLocation];
+		point = frameToContentRect(privateData->window, point);
+
+		bs::Vector2I position((int32_t)point.x, (int32_t)point.y);
+
+		if(bs::CocoaDragAndDrop::_notifyDragDropped(mWindow, position, paths))
+			return YES;
+	}
+
+	return NO;
+}
+@end
+
+namespace bs
+{
+	CocoaWindow::CocoaWindow(const WINDOW_DESC& desc)
+	{ @autoreleasepool {
+		m = bs_new<Pimpl>();
+
+		NSArray* screens = [NSScreen screens];
+
+		NSScreen* screen = nil;
+		INT32 x = 0;
+		INT32 y = 0;
+
+		for(NSScreen* entry in screens)
+		{
+			NSRect screenRect = [entry frame];
+
+			if(((desc.x >= screenRect.origin.x && desc.y < (screenRect.origin.x + screenRect.size.width)) || desc.x == -1) &&
+					((desc.y >= screenRect.origin.y && desc.y < (screenRect.origin.y + screenRect.size.height)) || desc.y == -1))
+			{
+				if(desc.x == -1)
+					x = (INT32)screenRect.origin.x + std::max(0, (INT32)screenRect.size.width - (INT32)desc.width) / 2;
+				else
+					x = desc.x - (INT32)screenRect.origin.x;
+
+				if(desc.y == -1)
+					y = (INT32)screenRect.origin.y + std::max(0, (INT32)screenRect.size.height - (INT32)desc.height) / 2;
+				else
+					y = desc.y - (INT32)screenRect.origin.y;
+
+				screen = entry;
+				break;
+			}
+		}
+
+		if(!desc.showDecorations)
+			m->style |= NSWindowStyleMaskBorderless;
+		else
+			m->style |= NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable;
+
+		if(desc.allowResize)
+			m->style |= NSWindowStyleMaskResizable;
+
+		m->window = [BSWindow alloc];
+		m->window = [m->window
+			initWithContentRect:NSMakeRect(x, y, desc.width, desc.height)
+			styleMask:(NSWindowStyleMask)m->style
+			backing:NSBackingStoreBuffered
+			defer:NO
+			screen:screen];
+
+		if(desc.allowResize)
+		{
+			bool allowSpaces = NSAppKitVersionNumber > NSAppKitVersionNumber10_6;
+			if(allowSpaces)
+				[m->window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
+		}
+
+		NSString* titleString = [NSString stringWithUTF8String:desc.title.c_str()];
+
+		[m->window setAcceptsMouseMovedEvents:YES];
+		[m->window setReleasedWhenClosed:NO];
+		[m->window setTitle:titleString];
+		[m->window makeKeyAndOrderFront:nil];
+
+		m->responder = [[BSWindowListener alloc] initWithOwner:this];
+		[m->window setNextResponder:m->responder];
+
+		m->delegate = [[BSWindowDelegate alloc] initWithWindow:this];
+		[m->window setDelegate:m->delegate];
+
+		m->view = [[BSView alloc] init];
+		[m->window setContentView:m->view];
+
+		if(desc.background)
+		{
+			[m->window setAlphaValue:1.0f];
+			[m->window setOpaque:NO];
+			[m->window setBackgroundColor:[NSColor clearColor]];
+
+			NSImage* image = MacOSPlatform::createNSImage(*desc.background);
+			[m->view setBackgroundImage:image];
+		}
+
+		m->isModal = desc.modal;
+		m->windowNumber = [m->window windowNumber];
+
+		MacOSPlatform::registerWindow(this);
+	}}
+
+	CocoaWindow::~CocoaWindow()
+	{
+		if(m->window != nil)
+			_destroy();
+
+		bs_delete(m);
+	}
+
+	void CocoaWindow::move(INT32 x, INT32 y)
+	{ @autoreleasepool {
+		NSPoint point;
+		point.x = x;
+		point.y = y;
+
+		[m->window setFrameTopLeftPoint:point];
+	}}
+
+	void CocoaWindow::resize(UINT32 width, UINT32 height)
+	{ @autoreleasepool {
+		NSRect frameRect = m->window.frame;
+		NSRect contentRect = [m->window contentRectForFrameRect:frameRect];
+
+		contentRect.size.width = width;
+		contentRect.size.height = height;
+
+		[m->window setFrame:[m->window frameRectForContentRect:contentRect] display:YES];
+	}}
+
+	void CocoaWindow::_destroy()
+	{
+		MacOSPlatform::unregisterWindow(this);
+		m->window = nil;
+	}
+
+	Rect2I CocoaWindow::getArea() const
+	{ @autoreleasepool {
+		NSRect frameRect = [m->window frame];
+		NSRect contentRect = [m->window contentRectForFrameRect:frameRect];
+
+		flipY([m->window screen], contentRect);
+
+		return Rect2I(
+			(INT32)contentRect.origin.x,
+			(INT32)contentRect.origin.y,
+			(UINT32)contentRect.size.width,
+			(UINT32)contentRect.size.height
+		);
+	}}
+
+	void CocoaWindow::hide()
+	{ @autoreleasepool {
+		[m->window orderOut:nil];
+	}}
+
+	void CocoaWindow::show()
+	{ @autoreleasepool {
+		[m->window makeKeyAndOrderFront:nil];
+	}}
+
+	void CocoaWindow::maximize()
+	{ @autoreleasepool {
+		if(![m->window isZoomed])
+			[m->window zoom:nil];
+	}}
+
+	void CocoaWindow::minimize()
+	{ @autoreleasepool {
+		[m->window miniaturize:nil];
+	}}
+
+	void CocoaWindow::restore()
+	{ @autoreleasepool {
+		if([m->window isMiniaturized])
+			[m->window deminiaturize:nil];
+		else if([m->window isZoomed])
+			[m->window zoom:nil];
+	}}
+
+	Vector2I CocoaWindow::windowToScreenPos(const Vector2I& windowPos) const
+	{ @autoreleasepool {
+		NSRect frameRect = [m->window frame];
+		NSRect contentRect = [m->window contentRectForFrameRect:frameRect];
+
+		flipY([m->window screen], contentRect);
+
+		Vector2I screenPos;
+		screenPos.x = windowPos.x + (INT32)contentRect.origin.x;
+		screenPos.y = windowPos.y + (INT32)contentRect.origin.y;
+
+		return screenPos;
+	}}
+
+	Vector2I CocoaWindow::screenToWindowPos(const Vector2I& screenPos) const
+	{ @autoreleasepool {
+		NSRect frameRect = [m->window frame];
+		NSRect contentRect = [m->window contentRectForFrameRect:frameRect];
+
+		flipY([m->window screen], contentRect);
+
+		Vector2I windowPos;
+		windowPos.x = screenPos.x - (INT32) contentRect.origin.x;
+		windowPos.y = screenPos.y - (INT32) contentRect.origin.y;
+
+		return windowPos;
+	}}
+
+	void CocoaWindow::setWindowed()
+	{
+		if(m->isFullscreen)
+		{
+			[m->window setStyleMask:(NSWindowStyleMask)m->style];
+			[m->window setFrame:m->windowedRect display:NO];
+			[m->window setLevel:NSNormalWindowLevel];
+
+			m->isFullscreen = false;
+		}
+	}
+
+	void CocoaWindow::setFullscreen()
+	{
+		if(!m->isFullscreen)
+			m->windowedRect = [m->window frame];
+
+		NSRect frame = [[m->window screen] frame];
+		[m->window setStyleMask:NSWindowStyleMaskBorderless];
+		[m->window setFrame:frame display:NO];
+		[m->window setLevel:NSMainMenuWindowLevel+1];
+		[m->window makeKeyAndOrderFront:nil];
+
+		m->isFullscreen = true;
+	}
+
+	void CocoaWindow::_setDragZones(const Vector<Rect2I>& rects)
+	{ @autoreleasepool {
+		NSMutableArray* array = [[NSMutableArray alloc] init];
+
+		for(auto& entry : rects)
+			[array addObject:[NSValue valueWithBytes:&entry objCType:@encode(Rect2I)]];
+
+		[m->responder setDragAreas:array];
+	}}
+
+	void CocoaWindow::_setUserData(void* data)
+	{
+		m->userData = data;
+	}
+
+	void* CocoaWindow::_getUserData() const
+	{
+		return m->userData;
+	}
+
+	void CocoaWindow::_registerForDragAndDrop()
+	{
+		if(m->numDropTargets == 0)
+			[m->window registerForDraggedTypes:@[NSFilenamesPboardType]];
+	}
+
+	void CocoaWindow::_unregisterForDragAndDrop()
+	{
+		if(m->numDropTargets == 0)
+			return;
+
+		m->numDropTargets--;
+
+		if(m->numDropTargets == 0)
+			[m->window unregisterDraggedTypes];
+	}
+}

+ 13 - 3
Source/BansheeCore/Platform/BsFolderMonitor.h

@@ -27,10 +27,11 @@ namespace bs
 	 */
 	 */
 	class BS_CORE_EXPORT FolderMonitor
 	class BS_CORE_EXPORT FolderMonitor
 	{
 	{
-		struct Pimpl;
 		class FileNotifyInfo;
 		class FileNotifyInfo;
-		struct FolderWatchInfo;
 	public:
 	public:
+		struct Pimpl;
+		struct FolderWatchInfo;
+
 		FolderMonitor();
 		FolderMonitor();
 		~FolderMonitor();
 		~FolderMonitor();
 
 
@@ -51,7 +52,7 @@ namespace bs
 		/**	Stops monitoring all folders that are currently being monitored. */
 		/**	Stops monitoring all folders that are currently being monitored. */
 		void stopMonitorAll();
 		void stopMonitorAll();
 
 
-		/** Callbacks will only get fired after update is called. */
+		/** Triggers callbacks depending on events that ocurred. Expected to be called once per frame. */
 		void _update();
 		void _update();
 
 
 		/** Triggers when a file in the monitored folder is modified. Provides absolute path to the file. */
 		/** Triggers when a file in the monitored folder is modified. Provides absolute path to the file. */
@@ -66,6 +67,15 @@ namespace bs
 		/**	Triggers when a file/folder is renamed in the monitored folder. Provides absolute path with old and new names. */
 		/**	Triggers when a file/folder is renamed in the monitored folder. Provides absolute path with old and new names. */
 		Event<void(const Path&, const Path&)> onRenamed;
 		Event<void(const Path&, const Path&)> onRenamed;
 
 
+		/**
+		 * @name Internal
+		 * @{
+		 */
+
+		/** Returns private data, for use by internal helper classes and methods. */
+		Pimpl* _getPrivateData() const { return m; }
+
+		/** @} */
 	private:
 	private:
 		/**	Worker method that monitors the IO ports for any modification notifications. */
 		/**	Worker method that monitors the IO ports for any modification notifications. */
 		void workerThreadMain();
 		void workerThreadMain();

+ 6 - 7
Source/BansheeCore/Profiling/BsProfilerCPU.cpp

@@ -14,7 +14,11 @@
 #endif
 #endif
 
 
 #if BS_COMPILER == BS_COMPILER_CLANG
 #if BS_COMPILER == BS_COMPILER_CLANG
-	#include "intrin.h"
+	#if BS_PLATFORM == BS_PLATFORM_WIN32
+		#include "intrin.h"
+	#else
+		#include <x86intrin.h>
+	#endif
 #endif
 #endif
 
 
 using namespace std::chrono;
 using namespace std::chrono;
@@ -72,7 +76,7 @@ namespace bs
 
 
 	inline UINT64 ProfilerCPU::TimerPrecise::getNumCycles() 
 	inline UINT64 ProfilerCPU::TimerPrecise::getNumCycles() 
 	{
 	{
-#if BS_COMPILER == BS_COMPILER_GNUC
+#if BS_COMPILER == BS_COMPILER_GNUC || BS_COMPILER == BS_COMPILER_CLANG
 		unsigned int a = 0;
 		unsigned int a = 0;
 		unsigned int b[4];
 		unsigned int b[4];
 		__get_cpuid(a, &b[0], &b[1], &b[2], &b[3]);
 		__get_cpuid(a, &b[0], &b[1], &b[2], &b[3]);
@@ -86,11 +90,6 @@ namespace bs
 		__asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
 		__asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
 		return x;
 		return x;
 #endif
 #endif
-#elif BS_COMPILER == BS_COMPILER_CLANG
-		UINT32 a = 0;
-		UINT32 b[4];
-		__get_cpuid(a, &b[0], &b[1], &b[2], &b[3]);
-		return __rdtsc();
 #elif BS_COMPILER == BS_COMPILER_MSVC
 #elif BS_COMPILER == BS_COMPILER_MSVC
 		int a[4];
 		int a[4];
 		int b = 0;
 		int b = 0;

+ 2 - 2
Source/BansheeCore/Profiling/BsProfilerCPU.h

@@ -151,7 +151,7 @@ namespace bs
 			 */
 			 */
 			void resumeLastSample();
 			void resumeLastSample();
 
 
-			Vector<PreciseProfileSample, StdFrameAlloc<ProfileSample>> samples;
+			Vector<PreciseProfileSample, StdFrameAlloc<PreciseProfileSample>> samples;
 			TimerPrecise timer;
 			TimerPrecise timer;
 
 
 			UINT64 memAllocs;
 			UINT64 memAllocs;
@@ -421,4 +421,4 @@ namespace bs
 	bs::gProfilerCPU().endSample(name);
 	bs::gProfilerCPU().endSample(name);
 
 
 	/** @} */
 	/** @} */
-}
+}

+ 1 - 1
Source/BansheeCore/RTTI/BsResourceRTTI.h

@@ -30,7 +30,7 @@ namespace bs
 			addReflectablePtrField("mMetaData", 1, &ResourceRTTI::getMetaData, &ResourceRTTI::setMetaData);
 			addReflectablePtrField("mMetaData", 1, &ResourceRTTI::getMetaData, &ResourceRTTI::setMetaData);
 		}
 		}
 
 
-		void onDeserializationStarted(IReflectable* obj, const UnorderedMap<bool, UINT64>& params)
+		void onDeserializationStarted(IReflectable* obj, const UnorderedMap<String, UINT64>& params) override
 		{
 		{
 			Resource* resource = static_cast<Resource*>(obj);
 			Resource* resource = static_cast<Resource*>(obj);
 
 

+ 0 - 3
Source/BansheeCore/Resources/BsResourceHandle.cpp

@@ -116,9 +116,6 @@ namespace bs
 #endif
 #endif
 	}
 	}
 
 
-	template class TResourceHandleBase<true>;
-	template class TResourceHandleBase<false>;
-
 	RTTITypeBase* TResourceHandleBase<true>::getRTTIStatic()
 	RTTITypeBase* TResourceHandleBase<true>::getRTTIStatic()
 	{
 	{
 		return WeakResourceHandleRTTI::instance();
 		return WeakResourceHandleRTTI::instance();

+ 30 - 14
Source/BansheeUtility/CMakeSources.cmake

@@ -21,19 +21,6 @@ set(BS_BANSHEEUTILITY_INC_PREREQUISITES
 	"Prerequisites/BsRTTIPrerequisites.h"
 	"Prerequisites/BsRTTIPrerequisites.h"
 )
 )
 
 
-set(BS_BANSHEEUTILITY_SRC_WIN32
-	"Win32/BsWin32FileSystem.cpp"
-	"Win32/BsWin32CrashHandler.cpp"
-	"Win32/BsWin32PlatformUtility.cpp"
-	"Win32/BsWin32Window.cpp"
-)
-
-set(BS_BANSHEEUTILITY_SRC_UNIX
-	"Linux/BsUnixCrashHandler.cpp"
-	"Linux/BsUnixFileSystem.cpp"
-	"Linux/BsUnixPlatformUtility.cpp"
-)
-
 set(BS_BANSHEEUTILITY_INC_IMAGE
 set(BS_BANSHEEUTILITY_INC_IMAGE
 	"Image/BsColor.h"
 	"Image/BsColor.h"
 	"Image/BsTextureAtlasLayout.h"
 	"Image/BsTextureAtlasLayout.h"
@@ -266,6 +253,27 @@ set(BS_BANSHEEUTILITY_INC_WIN32
 	"Win32/BsWin32Window.h"
 	"Win32/BsWin32Window.h"
 )
 )
 
 
+set(BS_BANSHEEUTILITY_SRC_WIN32
+	"Win32/BsWin32FileSystem.cpp"
+	"Win32/BsWin32CrashHandler.cpp"
+	"Win32/BsWin32PlatformUtility.cpp"
+	"Win32/BsWin32Window.cpp"
+)
+
+set(BS_BANSHEEUTILITY_SRC_UNIX
+	"Unix/BsUnixFileSystem.cpp"
+)
+
+set(BS_BANSHEEUTILITY_SRC_LINUX
+	"Linux/BsUnixCrashHandler.cpp"
+	"Linux/BsUnixPlatformUtility.cpp"
+)
+
+set(BS_BANSHEEUTILITY_SRC_MACOS
+	"MacOS/BsMacOSCrashHandler.cpp"
+	"MacOS/BsMacOSPlatformUtility.cpp"
+)
+
 source_group("Header Files\\Threading" FILES ${BS_BANSHEEUTILITY_INC_THREADING})
 source_group("Header Files\\Threading" FILES ${BS_BANSHEEUTILITY_INC_THREADING})
 source_group("Source Files\\ThirdParty" FILES ${BS_BANSHEEUTILITY_SRC_THIRDPARTY})
 source_group("Source Files\\ThirdParty" FILES ${BS_BANSHEEUTILITY_SRC_THIRDPARTY})
 source_group("Header Files\\Prerequisites" FILES ${BS_BANSHEEUTILITY_INC_PREREQUISITES})
 source_group("Header Files\\Prerequisites" FILES ${BS_BANSHEEUTILITY_INC_PREREQUISITES})
@@ -331,6 +339,14 @@ set(BS_BANSHEEUTILITY_SRC
 if(WIN32)
 if(WIN32)
 	list(APPEND BS_BANSHEEUTILITY_SRC ${BS_BANSHEEUTILITY_SRC_WIN32})
 	list(APPEND BS_BANSHEEUTILITY_SRC ${BS_BANSHEEUTILITY_SRC_WIN32})
 	list(APPEND BS_BANSHEEUTILITY_SRC ${BS_BANSHEEUTILITY_INC_WIN32})
 	list(APPEND BS_BANSHEEUTILITY_SRC ${BS_BANSHEEUTILITY_INC_WIN32})
-elseif(LINUX)
+endif()
+
+if(UNIX)
 	list(APPEND BS_BANSHEEUTILITY_SRC ${BS_BANSHEEUTILITY_SRC_UNIX})
 	list(APPEND BS_BANSHEEUTILITY_SRC ${BS_BANSHEEUTILITY_SRC_UNIX})
+
+	if(LINUX)
+		list(APPEND BS_BANSHEEUTILITY_SRC ${BS_BANSHEEUTILITY_SRC_LINUX})
+	elseif(APPLE)
+		list(APPEND BS_BANSHEEUTILITY_SRC ${BS_BANSHEEUTILITY_SRC_MACOS})
+	endif()
 endif()
 endif()

+ 0 - 0
Source/BansheeUtility/Linux/BsUnixCrashHandler.cpp → Source/BansheeUtility/Linux/BsLinuxCrashHandler.cpp


+ 0 - 0
Source/BansheeUtility/Linux/BsUnixPlatformUtility.cpp → Source/BansheeUtility/Linux/BsLinuxPlatformUtility.cpp


+ 94 - 0
Source/BansheeUtility/MacOS/BsMacOSCrashHandler.cpp

@@ -0,0 +1,94 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#include "Prerequisites/BsPrerequisitesUtil.h"
+
+#include <cxxabi.h>
+#include <execinfo.h>
+#include <dlfcn.h>
+#include <csignal>
+
+namespace bs
+{
+	CrashHandler::CrashHandler() {}
+	CrashHandler::~CrashHandler() {}
+
+	String CrashHandler::getCrashTimestamp()
+	{
+		std::time_t t = time(0);
+		struct tm *now = localtime(&t);
+
+		String timeStamp = "{0}{1}{2}_{3}{4}";
+		String strYear = toString(now->tm_year, 4, '0');
+		String strMonth = toString(now->tm_mon, 2, '0');
+		String strDay = toString(now->tm_mday, 2, '0');
+		String strHour = toString(now->tm_hour, 2, '0');
+		String strMinute = toString(now->tm_min, 2, '0');
+		return StringUtil::format(timeStamp, strYear, strMonth, strDay, strHour, strMinute);
+	}
+
+	String CrashHandler::getStackTrace()
+	{
+		StringStream stackTrace;
+		void* trace[BS_MAX_STACKTRACE_DEPTH];
+
+		int traceSize = backtrace(trace, BS_MAX_STACKTRACE_DEPTH);
+		char** messages = backtrace_symbols(trace, traceSize);
+
+		for (int i = 0; i < traceSize && messages != nullptr; ++i)
+		{
+			stackTrace << std::to_string(i) << ") " << messages[i];
+
+			// Try parsing a human readable name
+			Dl_info info;
+			if (dladdr(trace[i], &info) && info.dli_sname)
+			{
+				stackTrace << ": ";
+
+				if (info.dli_sname[0] == '_')
+				{
+					int status = -1;
+					char* demangledName = abi::__cxa_demangle(info.dli_sname, nullptr, nullptr, &status);
+
+					if(status == 0)
+						stackTrace << demangledName;
+					else
+						stackTrace << info.dli_sname;
+
+					free(demangledName);
+				}
+				else
+					stackTrace << info.dli_sname;
+
+				// Try to find the line number
+				for (char *p = messages[i]; *p; ++p)
+				{
+					if (*p == '+')
+					{
+						stackTrace << " " << p;
+						break;
+					}
+				}
+			}
+
+			if (i < traceSize - 1)
+				stackTrace << "\n";
+		}
+
+		free(messages);
+
+		return stackTrace.str();
+	}
+
+	void CrashHandler::reportCrash(const String& type,
+								   const String& description,
+								   const String& function,
+								   const String& file,
+								   UINT32 line) const
+	{
+		logErrorAndStackTrace(type, description, function, file, line);
+		saveCrashLog();
+
+		// Allow the debugger a chance to attach
+		std::raise(SIGINT);
+	}
+}

+ 81 - 0
Source/BansheeUtility/MacOS/BsMacOSPlatformUtility.cpp

@@ -0,0 +1,81 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#include "Utility/BsPlatformUtility.h"
+#include <uuid/uuid.h>
+#include <sys/sysctl.h>
+
+namespace bs
+{
+	GPUInfo PlatformUtility::sGPUInfo;
+
+	void PlatformUtility::terminate(bool force)
+	{
+		// TODOPORT - Support clean exit by sending the main window a quit message
+		exit(0);
+	}
+
+	SystemInfo PlatformUtility::getSystemInfo()
+	{
+		char buffer[256];
+
+		SystemInfo sysInfo;
+
+		size_t bufferlen = sizeof(buffer);
+		if(sysctlbyname("machdep.cpu.vendor", buffer, &bufferlen, nullptr, 0) == 0)
+			sysInfo.cpuManufacturer = buffer;
+
+		bufferlen = sizeof(buffer);
+		if(sysctlbyname("machdep.cpu.brand_string", buffer, &bufferlen, nullptr, 0) == 0)
+			sysInfo.cpuModel = buffer;
+
+		bufferlen = sizeof(buffer);
+		if(sysctlbyname("kern.osrelease", buffer, &bufferlen, nullptr, 0) == 0)
+			sysInfo.osName = "macOS " + String(buffer);
+
+		bufferlen = sizeof(buffer);
+		if(sysctlbyname("kern.version", buffer, &bufferlen, nullptr, 0) == 0)
+			sysInfo.osIs64Bit = strstr(buffer, "X86_64") != nullptr;
+		else
+			sysInfo.osIs64Bit = false;
+
+		bufferlen = sizeof(buffer);
+		if(sysctlbyname("hw.cpufrequency", buffer, &bufferlen, nullptr, 0) == 0)
+		{
+			UINT32 speedHz = *(UINT32*)buffer;
+			sysInfo.cpuClockSpeedMhz = speedHz / (1000 * 1000);
+		}
+		else
+			sysInfo.cpuClockSpeedMhz = 0;
+
+		bufferlen = sizeof(buffer);
+		if(sysctlbyname("hw.physicalcpu", buffer, &bufferlen, nullptr, 0) == 0)
+			sysInfo.cpuNumCores = *(UINT32*)buffer;
+		else
+			sysInfo.cpuNumCores = 0;
+
+		bufferlen = sizeof(buffer);
+		if(sysctlbyname("hw.memsize", buffer, &bufferlen, nullptr, 0) == 0)
+		{
+			UINT64 memAmountBytes = *(UINT64*)buffer;
+			sysInfo.memoryAmountMb = (UINT32)(memAmountBytes / (1024 * 1024));
+		}
+		else
+			sysInfo.memoryAmountMb = 0;
+
+		sysInfo.gpuInfo = sGPUInfo;
+		return sysInfo;
+	}
+
+	UUID PlatformUtility::generateUUID()
+	{
+		uuid_t nativeUUID;
+		uuid_generate(nativeUUID);
+
+		return UUID(
+			*(UINT32*)&nativeUUID[0],
+			*(UINT32*)&nativeUUID[4],
+			*(UINT32*)&nativeUUID[8],
+			*(UINT32*)&nativeUUID[12]);
+	}
+}
+

+ 1 - 1
Source/BansheeUtility/Math/BsRect3.cpp

@@ -26,7 +26,7 @@ namespace bs
 
 
 		bool foundNearest = false;
 		bool foundNearest = false;
 		float t = 0.0f;
 		float t = 0.0f;
-		std::array<Vector3, 2> nearestPoints { Vector3::ZERO, Vector3::ZERO };
+		std::array<Vector3, 2> nearestPoints {{ Vector3::ZERO, Vector3::ZERO }};
 		float distance = 0.0f;
 		float distance = 0.0f;
 
 
 		// Check if Ray intersects the rectangle
 		// Check if Ray intersects the rectangle

+ 2 - 0
Source/BansheeUtility/Reflection/BsRTTIField.h

@@ -95,6 +95,8 @@ namespace bs
 		SerializableFieldType mType;
 		SerializableFieldType mType;
 		UINT64 mFlags;
 		UINT64 mFlags;
 
 
+		virtual ~RTTIField() { }
+
 		/** Checks is the field plain type and castable to RTTIPlainFieldBase. */
 		/** Checks is the field plain type and castable to RTTIPlainFieldBase. */
 		bool isPlainType() const { return mType == SerializableFT_Plain; }
 		bool isPlainType() const { return mType == SerializableFT_Plain; }
 
 

+ 0 - 3
Source/BansheeUtility/String/BsStringID.cpp

@@ -137,9 +137,6 @@ namespace bs
 		static bool compare(String const& a, char* b) { return a.compare(b) == 0; }
 		static bool compare(String const& a, char* b) { return a.compare(b) == 0; }
 	};
 	};
 
 
-	template class StringID::StringIDUtil <const char*>;
-	template class StringID::StringIDUtil <String>;
-
 	template BS_UTILITY_EXPORT void StringID::construct(const char* const&);
 	template BS_UTILITY_EXPORT void StringID::construct(const char* const&);
 	template BS_UTILITY_EXPORT void StringID::construct(String const&);
 	template BS_UTILITY_EXPORT void StringID::construct(String const&);
 	
 	

+ 0 - 0
Source/BansheeUtility/Linux/BsUnixFileSystem.cpp → Source/BansheeUtility/Unix/BsUnixFileSystem.cpp


+ 1 - 1
Source/BansheeUtility/Utility/BsUUID.cpp

@@ -34,7 +34,7 @@ namespace bs
 		return output;
 		return output;
 	}
 	}
 
 
-	static const std::array<char, 16> HEX_TO_LITERAL = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+	static const std::array<char, 16> HEX_TO_LITERAL = {{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}};
 	static const std::array<UINT8, 256> LITERAL_TO_HEX = literalToHex();
 	static const std::array<UINT8, 256> LITERAL_TO_HEX = literalToHex();
 
 
 	UUID UUID::EMPTY;
 	UUID UUID::EMPTY;

+ 10 - 0
Source/CMake/Common.cmake

@@ -153,6 +153,16 @@ MACRO(end_find_package FOLDER_NAME MAIN_LIB_NAME)
 	set(${FOLDER_NAME}_INCLUDE_DIRS ${${FOLDER_NAME}_INCLUDE_DIR})
 	set(${FOLDER_NAME}_INCLUDE_DIRS ${${FOLDER_NAME}_INCLUDE_DIR})
 ENDMACRO()
 ENDMACRO()
 
 
+function(target_link_framework TARGET FRAMEWORK)
+	find_library(FM_${FRAMEWORK} ${FRAMEWORK})
+
+	if(NOT FM_${FRAMEWORK})
+		message(FATAL_ERROR "Cannot find ${FRAMEWORK} framework.")
+	endif()
+
+	target_link_libraries(${TARGET} PRIVATE ${FM_${FRAMEWORK}})
+endfunction()
+
 function(update_binary_deps DEP_VERSION)
 function(update_binary_deps DEP_VERSION)
 	# Clean and create a temporary folder
 	# Clean and create a temporary folder
 	execute_process(COMMAND ${CMAKE_COMMAND} -E remove_directory ${PROJECT_SOURCE_DIR}/../Temp)	
 	execute_process(COMMAND ${CMAKE_COMMAND} -E remove_directory ${PROJECT_SOURCE_DIR}/../Temp)	

+ 8 - 1
Source/CMake/GenerateScriptBindings.cmake

@@ -73,9 +73,16 @@ if(BansheeSBGen_FOUND)
 			-output-cs-editor ${BS_GENERATED_CS_EDITOR_OUTPUT_DIR}
 			-output-cs-editor ${BS_GENERATED_CS_EDITOR_OUTPUT_DIR}
 			-- ${BS_INCLUDE_DIRS}
 			-- ${BS_INCLUDE_DIRS}
 			-std=c++14
 			-std=c++14
+			-stdlib=libc++
 			-DBS_STATIC_LIB
 			-DBS_STATIC_LIB
 			-DBS_SBGEN
 			-DBS_SBGEN
-			-w)
+			-w
+			)
+
+		if(APPLE)
+			list(APPEND BS_GSB_COMMAND 
+				-isystem /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1)
+		endif()
 
 
 		message(STATUS "Generating script bindings, please wait...")
 		message(STATUS "Generating script bindings, please wait...")
 		execute_process(
 		execute_process(

+ 6 - 4
Source/CMake/Modules/FindPhysX.cmake

@@ -10,10 +10,12 @@ start_find_package(PhysX)
 set(PhysX_INSTALL_DIR ${PROJECT_SOURCE_DIR}/../Dependencies/PhysX CACHE PATH "")
 set(PhysX_INSTALL_DIR ${PROJECT_SOURCE_DIR}/../Dependencies/PhysX CACHE PATH "")
 gen_default_lib_search_dirs(PhysX)
 gen_default_lib_search_dirs(PhysX)
 
 
-if(BS_64BIT)
-	set(BS_PHYSX_SUFFIX _x64)
-else()
-	set(BS_PHYSX_SUFFIX _x86)
+if(NOT APPLE)
+	if(BS_64BIT)
+		set(BS_PHYSX_SUFFIX _x64)
+	else()
+		set(BS_PHYSX_SUFFIX _x86)
+	endif()
 endif()
 endif()
 
 
 find_imported_includes(PhysX PxPhysics.h)
 find_imported_includes(PhysX PxPhysics.h)

+ 7 - 2
Source/CMake/Modules/Findmcs.cmake

@@ -6,8 +6,13 @@
 #  mcs_FOUND
 #  mcs_FOUND
 
 
 message(STATUS "Looking for Mono compiler (mcs) and xbuild installation...")
 message(STATUS "Looking for Mono compiler (mcs) and xbuild installation...")
-find_program(mcs_EXECUTABLE NAMES mcs)
-find_program(xbuild_EXECUTABLE NAMES xbuild)
+
+if(APPLE)
+	set(MONO_SEARCH_PATH /Library/Frameworks/Mono.framework/Versions/Current/Commands/)
+endif()
+
+find_program(mcs_EXECUTABLE NAMES mcs HINTS ${MONO_SEARCH_PATH})
+find_program(xbuild_EXECUTABLE NAMES xbuild HINTS ${MONO_SEARCH_PATH})
 
 
 if(mcs_EXECUTABLE AND xbuild_EXECUTABLE)
 if(mcs_EXECUTABLE AND xbuild_EXECUTABLE)
 	set(mcs_FOUND TRUE)
 	set(mcs_FOUND TRUE)

+ 19 - 5
Source/CMakeLists.txt

@@ -67,6 +67,7 @@ include(CMake/Common.cmake)
 
 
 # Ensure dependencies are up to date
 # Ensure dependencies are up to date
 ## Check prebuilt dependencies that are downloaded in a .zip
 ## Check prebuilt dependencies that are downloaded in a .zip
+if(NOT APPLE)
 set(BUILTIN_DEP_VERSION_FILE ${PROJECT_SOURCE_DIR}/../Dependencies/.version)
 set(BUILTIN_DEP_VERSION_FILE ${PROJECT_SOURCE_DIR}/../Dependencies/.version)
 if(NOT EXISTS ${BUILTIN_DEP_VERSION_FILE})
 if(NOT EXISTS ${BUILTIN_DEP_VERSION_FILE})
 	message(STATUS "Binary dependencies are missing. Downloading package...")
 	message(STATUS "Binary dependencies are missing. Downloading package...")
@@ -78,6 +79,7 @@ else()
 		update_binary_deps(${BS_PREBUILT_DEPENDENCIES_VERSION})
 		update_binary_deps(${BS_PREBUILT_DEPENDENCIES_VERSION})
 	endif()
 	endif()
 endif()
 endif()
+endif()
 
 
 ## Check dependencies built from source
 ## Check dependencies built from source
 if(WIN32)
 if(WIN32)
@@ -161,11 +163,17 @@ if(MSVC)
 	# Global defines
 	# Global defines
 	#add_definitions(-D_HAS_EXCEPTIONS=0)
 	#add_definitions(-D_HAS_EXCEPTIONS=0)
 	
 	
-elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "AppleClang")
 	# Note: Optionally add -ffunction-sections, -fdata-sections, but with linker option --gc-sections
 	# Note: Optionally add -ffunction-sections, -fdata-sections, but with linker option --gc-sections
 	# TODO: Use link-time optimization -flto. Might require non-default linker.
 	# TODO: Use link-time optimization -flto. Might require non-default linker.
-	set(BS_COMPILER_FLAGS_COMMON "-Wall -fPIC -fno-exceptions -fno-strict-aliasing -fno-rtti -fno-ms-compatibility -fms-extensions -Wl,-rpath=$ORIGIN")
-	
+	set(BS_COMPILER_FLAGS_COMMON "-Wall -fPIC -fno-exceptions -fno-strict-aliasing -fno-rtti -fno-ms-compatibility")
+
+	if(LINUX)
+		set(BS_COMPILER_FLAGS_COMMON "${BS_COMPILER_FLAGS_COMMON} -Wl,-rpath=$ORIGIN")
+	elseif(APPLE)
+		set(BS_COMPILER_FLAGS_COMMON "${BS_COMPILER_FLAGS_COMMON} -fobjc-arc")
+	endif()
+
 	set(CMAKE_CXX_FLAGS_DEBUG "${BS_COMPILER_FLAGS_COMMON} -g -O0 -DDEBUG")
 	set(CMAKE_CXX_FLAGS_DEBUG "${BS_COMPILER_FLAGS_COMMON} -g -O0 -DDEBUG")
 	set(CMAKE_CXX_FLAGS_OPTIMIZEDDEBUG "${BS_COMPILER_FLAGS_COMMON} -gline-tables-only -O2")
 	set(CMAKE_CXX_FLAGS_OPTIMIZEDDEBUG "${BS_COMPILER_FLAGS_COMMON} -gline-tables-only -O2")
 	set(CMAKE_CXX_FLAGS_RELEASE "${BS_COMPILER_FLAGS_COMMON} -g0 -O2")
 	set(CMAKE_CXX_FLAGS_RELEASE "${BS_COMPILER_FLAGS_COMMON} -g0 -O2")
@@ -177,8 +185,12 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
 	set(CMAKE_EXE_LINKER_FLAGS_RELEASE "-s -no-pie")
 	set(CMAKE_EXE_LINKER_FLAGS_RELEASE "-s -no-pie")
 elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
 elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
 	# TODO: Use link-time optimization -flto. Might require non-default linker.
 	# TODO: Use link-time optimization -flto. Might require non-default linker.
-	set(BS_COMPILER_FLAGS_COMMON "-Wall -fPIC -fno-exceptions -fno-strict-aliasing -fno-rtti -Wl,-rpath=$ORIGIN")
-	
+	set(BS_COMPILER_FLAGS_COMMON "-Wall -fPIC -fno-exceptions -fno-strict-aliasing -fno-rtti")
+
+	if(LINUX)
+		set(BS_COMPILER_FLAGS_COMMON "${BS_COMPILER_FLAGS_COMMON} -Wl,-rpath=$ORIGIN")
+	endif()
+
 	set(CMAKE_CXX_FLAGS_DEBUG "${BS_COMPILER_FLAGS_COMMON} -g -O0 -DDEBUG")
 	set(CMAKE_CXX_FLAGS_DEBUG "${BS_COMPILER_FLAGS_COMMON} -g -O0 -DDEBUG")
 	set(CMAKE_CXX_FLAGS_OPTIMIZEDDEBUG "${BS_COMPILER_FLAGS_COMMON} -gline-tables-only -O2")
 	set(CMAKE_CXX_FLAGS_OPTIMIZEDDEBUG "${BS_COMPILER_FLAGS_COMMON} -gline-tables-only -O2")
 	set(CMAKE_CXX_FLAGS_RELEASE "${BS_COMPILER_FLAGS_COMMON} -g0 -O2")
 	set(CMAKE_CXX_FLAGS_RELEASE "${BS_COMPILER_FLAGS_COMMON} -g0 -O2")
@@ -192,6 +204,8 @@ else()
 # TODO_OTHER_COMPILERS_GO_HERE
 # TODO_OTHER_COMPILERS_GO_HERE
 endif()
 endif()
 
 
+set(CMAKE_XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC "YES")
+
 # Output
 # Output
 if(BS_64BIT)
 if(BS_64BIT)
 	set(BS_OUTPUT_DIR_PREFIX x64)
 	set(BS_OUTPUT_DIR_PREFIX x64)