Browse Source

Hooked up mouse double click from the OS
GUI mouse drag is not delayed until user drags the mouse a few pixels instead of coinciding with mouse click

Marko Pintera 12 years ago
parent
commit
ebbe4cd225

+ 14 - 4
BansheeEngine/Include/BsGUIManager.h

@@ -16,6 +16,13 @@ namespace BansheeEngine
 	 */
 	 */
 	class BS_EXPORT GUIManager : public CM::Module<GUIManager>
 	class BS_EXPORT GUIManager : public CM::Module<GUIManager>
 	{
 	{
+		enum class DragState
+		{
+			NoDrag,
+			HeldWithoutDrag,
+			Dragging
+		};
+
 		struct GUIRenderData
 		struct GUIRenderData
 		{
 		{
 			GUIRenderData()
 			GUIRenderData()
@@ -80,8 +87,6 @@ namespace BansheeEngine
 		boost::signal<void(GUIWidget*, GUIElement*, const GUIMouseEvent&)> mouseEventFilter;
 		boost::signal<void(GUIWidget*, GUIElement*, const GUIMouseEvent&)> mouseEventFilter;
 		boost::signal<void(GUIWidget*, GUIElement*, const GUITextInputEvent&)> textInputEventFilter;
 		boost::signal<void(GUIWidget*, GUIElement*, const GUITextInputEvent&)> textInputEventFilter;
 	private:
 	private:
-		static float DOUBLE_CLICK_INTERVAL;
-
 		struct SelectiveInputData
 		struct SelectiveInputData
 		{
 		{
 			SelectiveInputData()
 			SelectiveInputData()
@@ -92,6 +97,8 @@ namespace BansheeEngine
 			bool acceptAllElements;
 			bool acceptAllElements;
 		};
 		};
 
 
+		static CM::UINT32 DRAG_DISTANCE;
+
 		CM::Vector<WidgetInfo>::type mWidgets;
 		CM::Vector<WidgetInfo>::type mWidgets;
 		CM::UnorderedMap<const CM::Viewport*, GUIRenderData>::type mCachedGUIData;
 		CM::UnorderedMap<const CM::Viewport*, GUIRenderData>::type mCachedGUIData;
 
 
@@ -113,11 +120,12 @@ namespace BansheeEngine
 		GUIInputCaret* mInputCaret;
 		GUIInputCaret* mInputCaret;
 		GUIInputSelection* mInputSelection;
 		GUIInputSelection* mInputSelection;
 
 
-		float mLastClickTime;
-
 		bool mSeparateMeshesByWidget;
 		bool mSeparateMeshesByWidget;
 		CM::Int2 mLastCursorLocalPos;
 		CM::Int2 mLastCursorLocalPos;
 
 
+		DragState mDragState;
+		CM::Int2 mLastCursorClickPos;
+
 		GUIMouseEvent mMouseEvent;
 		GUIMouseEvent mMouseEvent;
 		GUITextInputEvent mTextInputEvent;
 		GUITextInputEvent mTextInputEvent;
 		GUICommandEvent mCommandEvent;
 		GUICommandEvent mCommandEvent;
@@ -139,6 +147,7 @@ namespace BansheeEngine
 		boost::signals::connection mOnCursorMovedConn;
 		boost::signals::connection mOnCursorMovedConn;
 		boost::signals::connection mOnCursorPressedConn;
 		boost::signals::connection mOnCursorPressedConn;
 		boost::signals::connection mOnCursorReleasedConn;
 		boost::signals::connection mOnCursorReleasedConn;
+		boost::signals::connection mOnCursorDoubleClick;
 		boost::signals::connection mOnTextInputConn;
 		boost::signals::connection mOnTextInputConn;
 		boost::signals::connection mOnInputCommandConn;
 		boost::signals::connection mOnInputCommandConn;
 
 
@@ -162,6 +171,7 @@ namespace BansheeEngine
 		void onCursorMoved(const CM::PositionalInputEvent& event);
 		void onCursorMoved(const CM::PositionalInputEvent& event);
 		void onCursorReleased(const CM::PositionalInputEvent& event);
 		void onCursorReleased(const CM::PositionalInputEvent& event);
 		void onCursorPressed(const CM::PositionalInputEvent& event);
 		void onCursorPressed(const CM::PositionalInputEvent& event);
+		void onCursorDoubleClick(const CM::PositionalInputEvent& event);
 		void onTextInput(const CM::TextInputEvent& event);
 		void onTextInput(const CM::TextInputEvent& event);
 		void onInputCommandEntered(CM::InputCommandType commandType);
 		void onInputCommandEntered(CM::InputCommandType commandType);
 
 

+ 65 - 28
BansheeEngine/Source/BsGUIManager.cpp

@@ -31,8 +31,6 @@
 using namespace CamelotFramework;
 using namespace CamelotFramework;
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
-	float GUIManager::DOUBLE_CLICK_INTERVAL = 0.5f;
-
 	struct GUIGroupElement
 	struct GUIGroupElement
 	{
 	{
 		GUIGroupElement()
 		GUIGroupElement()
@@ -55,15 +53,18 @@ namespace BansheeEngine
 		Vector<GUIGroupElement>::type elements;
 		Vector<GUIGroupElement>::type elements;
 	};
 	};
 
 
+	UINT32 GUIManager::DRAG_DISTANCE = 3;
+
 	GUIManager::GUIManager()
 	GUIManager::GUIManager()
 		:mElementUnderCursor(nullptr), mWidgetUnderCursor(nullptr), mSeparateMeshesByWidget(true), mActiveElement(nullptr), 
 		:mElementUnderCursor(nullptr), mWidgetUnderCursor(nullptr), mSeparateMeshesByWidget(true), mActiveElement(nullptr), 
 		mActiveWidget(nullptr), mActiveMouseButton(GUIMouseButton::Left), mKeyboardFocusElement(nullptr), mKeyboardFocusWidget(nullptr),
 		mActiveWidget(nullptr), mActiveMouseButton(GUIMouseButton::Left), mKeyboardFocusElement(nullptr), mKeyboardFocusWidget(nullptr),
 		mCaretTexture(nullptr), mCaretBlinkInterval(0.5f), mCaretLastBlinkTime(0.0f), mCaretColor(1.0f, 0.6588f, 0.0f), mIsCaretOn(false),
 		mCaretTexture(nullptr), mCaretBlinkInterval(0.5f), mCaretLastBlinkTime(0.0f), mCaretColor(1.0f, 0.6588f, 0.0f), mIsCaretOn(false),
-		mTextSelectionColor(1.0f, 0.6588f, 0.0f), mInputCaret(nullptr), mInputSelection(nullptr), mSelectiveInputActive(false), mLastClickTime(0.0f)
+		mTextSelectionColor(1.0f, 0.6588f, 0.0f), mInputCaret(nullptr), mInputSelection(nullptr), mSelectiveInputActive(false), mDragState(DragState::NoDrag)
 	{
 	{
 		mOnCursorMovedConn = gInput().onCursorMoved.connect(boost::bind(&GUIManager::onCursorMoved, this, _1));
 		mOnCursorMovedConn = gInput().onCursorMoved.connect(boost::bind(&GUIManager::onCursorMoved, this, _1));
 		mOnCursorPressedConn = gInput().onCursorPressed.connect(boost::bind(&GUIManager::onCursorPressed, this, _1));
 		mOnCursorPressedConn = gInput().onCursorPressed.connect(boost::bind(&GUIManager::onCursorPressed, this, _1));
 		mOnCursorReleasedConn = gInput().onCursorReleased.connect(boost::bind(&GUIManager::onCursorReleased, this, _1));
 		mOnCursorReleasedConn = gInput().onCursorReleased.connect(boost::bind(&GUIManager::onCursorReleased, this, _1));
+		mOnCursorDoubleClick = gInput().onDoubleClick.connect(boost::bind(&GUIManager::onCursorDoubleClick, this, _1));
 		mOnTextInputConn = gInput().onCharInput.connect(boost::bind(&GUIManager::onTextInput, this, _1)); 
 		mOnTextInputConn = gInput().onCharInput.connect(boost::bind(&GUIManager::onTextInput, this, _1)); 
 		mOnInputCommandConn = gInput().onInputCommand.connect(boost::bind(&GUIManager::onInputCommandEntered, this, _1)); 
 		mOnInputCommandConn = gInput().onInputCommand.connect(boost::bind(&GUIManager::onInputCommandEntered, this, _1)); 
 
 
@@ -100,6 +101,7 @@ namespace BansheeEngine
 		mOnCursorPressedConn.disconnect();
 		mOnCursorPressedConn.disconnect();
 		mOnCursorReleasedConn.disconnect();
 		mOnCursorReleasedConn.disconnect();
 		mOnCursorMovedConn.disconnect();
 		mOnCursorMovedConn.disconnect();
+		mOnCursorDoubleClick.disconnect();
 		mOnTextInputConn.disconnect();
 		mOnTextInputConn.disconnect();
 		mOnInputCommandConn.disconnect();
 		mOnInputCommandConn.disconnect();
 
 
@@ -578,8 +580,22 @@ namespace BansheeEngine
 		if(mWidgetUnderCursor != nullptr)
 		if(mWidgetUnderCursor != nullptr)
 			localPos = getWidgetRelativePos(*mWidgetUnderCursor, event.screenPos);
 			localPos = getWidgetRelativePos(*mWidgetUnderCursor, event.screenPos);
 
 
+		if(mActiveElement != nullptr && mDragState == DragState::HeldWithoutDrag)
+		{
+			UINT32 dist = mLastCursorClickPos.manhattanDist(event.screenPos);
+
+			if(dist > DRAG_DISTANCE)
+			{
+				mMouseEvent.setMouseDragStartData(mElementUnderCursor, localPos);
+				if(sendMouseEvent(mActiveWidget, mActiveElement, mMouseEvent))
+					event.markAsUsed();
+
+				mDragState = DragState::Dragging;
+			}
+		}
+
 		// If mouse is being held down send MouseDrag events
 		// If mouse is being held down send MouseDrag events
-		if(mActiveElement != nullptr && mActiveMouseButton == GUIMouseButton::Left)
+		if(mActiveElement != nullptr && mDragState == DragState::Dragging)
 		{
 		{
 			Int2 curLocalPos = getWidgetRelativePos(*mActiveWidget, event.screenPos);
 			Int2 curLocalPos = getWidgetRelativePos(*mActiveWidget, event.screenPos);
 
 
@@ -662,16 +678,20 @@ namespace BansheeEngine
 		}
 		}
 
 
 		// Send DragEnd event to whichever element is active
 		// Send DragEnd event to whichever element is active
-		bool acceptEndDrag = mActiveMouseButton == guiButton && mActiveElement != nullptr;
+		bool acceptEndDrag = mDragState == DragState::Dragging && mActiveMouseButton == guiButton && 
+			mActiveElement != nullptr && (guiButton == GUIMouseButton::Left);
+
 		if(acceptEndDrag)
 		if(acceptEndDrag)
 		{
 		{
-			if(guiButton == GUIMouseButton::Left)
-			{
-				mMouseEvent.setMouseDragEndData(mElementUnderCursor, localPos);
-				if(sendMouseEvent(mActiveWidget, mActiveElement, mMouseEvent))
-					event.markAsUsed();
-			}
+			mMouseEvent.setMouseDragEndData(mElementUnderCursor, localPos);
+			if(sendMouseEvent(mActiveWidget, mActiveElement, mMouseEvent))
+				event.markAsUsed();
 
 
+			mDragState = DragState::NoDrag;
+		}
+
+		if(mActiveMouseButton == guiButton)
+		{
 			mActiveElement = nullptr;
 			mActiveElement = nullptr;
 			mActiveWidget = nullptr;
 			mActiveWidget = nullptr;
 			mActiveMouseButton = GUIMouseButton::Left;
 			mActiveMouseButton = GUIMouseButton::Left;
@@ -708,26 +728,14 @@ namespace BansheeEngine
 		{
 		{
 			Int2 localPos = getWidgetRelativePos(*mWidgetUnderCursor, event.screenPos);
 			Int2 localPos = getWidgetRelativePos(*mWidgetUnderCursor, event.screenPos);
 
 
-			if((mLastClickTime + DOUBLE_CLICK_INTERVAL) > gTime().getTime())
-			{
-				mMouseEvent.setMouseDoubleClickData(mElementUnderCursor, localPos, guiButton);
-				if(sendMouseEvent(mWidgetUnderCursor, mElementUnderCursor, mMouseEvent))
-					event.markAsUsed();
-			}
-			else
-			{
-				mMouseEvent.setMouseDownData(mElementUnderCursor, localPos, guiButton);
-				if(sendMouseEvent(mWidgetUnderCursor, mElementUnderCursor, mMouseEvent))
-					event.markAsUsed();
-
-				mLastClickTime = gTime().getTime();
-			}
+			mMouseEvent.setMouseDownData(mElementUnderCursor, localPos, guiButton);
+			if(sendMouseEvent(mWidgetUnderCursor, mElementUnderCursor, mMouseEvent))
+				event.markAsUsed();
 
 
 			if(guiButton == GUIMouseButton::Left)
 			if(guiButton == GUIMouseButton::Left)
 			{
 			{
-				mMouseEvent.setMouseDragStartData(mElementUnderCursor, localPos);
-				if(sendMouseEvent(mWidgetUnderCursor, mElementUnderCursor, mMouseEvent))
-					event.markAsUsed();
+				mDragState = DragState::HeldWithoutDrag;
+				mLastCursorClickPos = event.screenPos;
 			}
 			}
 
 
 			mActiveElement = mElementUnderCursor;
 			mActiveElement = mElementUnderCursor;
@@ -764,6 +772,35 @@ namespace BansheeEngine
 		}
 		}
 	}
 	}
 
 
+	void GUIManager::onCursorDoubleClick(const CM::PositionalInputEvent& event)
+	{
+		if(event.isUsed())
+			return;
+
+		bool buttonStates[(int)GUIMouseButton::Count];
+		buttonStates[0] = event.buttonStates[0];
+		buttonStates[1] = event.buttonStates[1];
+		buttonStates[2] = event.buttonStates[2];
+
+		if(findElementUnderCursor(event.screenPos, buttonStates, event.shift, event.control, event.alt))
+			event.markAsUsed();
+
+		mMouseEvent = GUIMouseEvent(buttonStates, event.shift, event.control, event.alt);
+
+		GUIMouseButton guiButton = buttonToGUIButton(event.button);
+
+		// We only check for mouse down if we are hovering over an element
+		bool acceptMouseDown = mElementUnderCursor != nullptr;
+		if(acceptMouseDown)
+		{
+			Int2 localPos = getWidgetRelativePos(*mWidgetUnderCursor, event.screenPos);
+
+			mMouseEvent.setMouseDoubleClickData(mElementUnderCursor, localPos, guiButton);
+			if(sendMouseEvent(mWidgetUnderCursor, mElementUnderCursor, mMouseEvent))
+				event.markAsUsed();
+		}
+	}
+
 	void GUIManager::onInputCommandEntered(CM::InputCommandType commandType)
 	void GUIManager::onInputCommandEntered(CM::InputCommandType commandType)
 	{
 	{
 		if(mKeyboardFocusElement == nullptr)
 		if(mKeyboardFocusElement == nullptr)

+ 6 - 0
CamelotCore/Include/CmInput.h

@@ -22,6 +22,7 @@ namespace CamelotFramework
 		boost::signal<void(const PositionalInputEvent&)> onCursorMoved;
 		boost::signal<void(const PositionalInputEvent&)> onCursorMoved;
 		boost::signal<void(const PositionalInputEvent&)> onCursorPressed;
 		boost::signal<void(const PositionalInputEvent&)> onCursorPressed;
 		boost::signal<void(const PositionalInputEvent&)> onCursorReleased;
 		boost::signal<void(const PositionalInputEvent&)> onCursorReleased;
+		boost::signal<void(const PositionalInputEvent&)> onDoubleClick;
 
 
 		boost::signal<void(InputCommandType)> onInputCommand;
 		boost::signal<void(InputCommandType)> onInputCommand;
 
 
@@ -92,6 +93,11 @@ namespace CamelotFramework
 		 */
 		 */
 		void cursorReleased(const PositionalInputEvent& event);
 		void cursorReleased(const PositionalInputEvent& event);
 		
 		
+		/**
+		 * @brief	Cursor button releases as OS reports it.
+		 */
+		void cursorDoubleClick(const PositionalInputEvent& event);
+
 		/**
 		/**
 		 * @brief	Input commands as OS reports them.
 		 * @brief	Input commands as OS reports them.
 		 */
 		 */

+ 2 - 1
CamelotCore/Include/CmInputFwd.h

@@ -261,7 +261,8 @@ namespace CamelotFramework
 	{
 	{
 		CursorMoved,
 		CursorMoved,
 		ButtonPressed,
 		ButtonPressed,
-		ButtonReleased
+		ButtonReleased,
+		DoubleClick
 	};
 	};
 
 
 	/**
 	/**

+ 14 - 0
CamelotCore/Include/CmOSInputHandler.h

@@ -24,6 +24,12 @@ namespace CamelotFramework
 			bool pressed;
 			bool pressed;
 		};
 		};
 
 
+		struct DoubleClick
+		{
+			Int2 cursorPos;
+			OSPositionalInputButtonStates btnStates;
+		};
+
 	public:
 	public:
 		OSInputHandler();
 		OSInputHandler();
 		virtual ~OSInputHandler();
 		virtual ~OSInputHandler();
@@ -33,6 +39,7 @@ namespace CamelotFramework
 		boost::signal<void(const PositionalInputEvent&)> onCursorMoved;
 		boost::signal<void(const PositionalInputEvent&)> onCursorMoved;
 		boost::signal<void(const PositionalInputEvent&)> onCursorPressed;
 		boost::signal<void(const PositionalInputEvent&)> onCursorPressed;
 		boost::signal<void(const PositionalInputEvent&)> onCursorReleased;
 		boost::signal<void(const PositionalInputEvent&)> onCursorReleased;
+		boost::signal<void(const PositionalInputEvent&)> onDoubleClick;
 		boost::signal<void(InputCommandType)> onInputCommand;
 		boost::signal<void(InputCommandType)> onInputCommand;
 
 
 		/**
 		/**
@@ -52,6 +59,7 @@ namespace CamelotFramework
 		float mMouseScroll;
 		float mMouseScroll;
 		WString mInputString;
 		WString mInputString;
 		Queue<ButtonStateChange>::type mButtonStates;
 		Queue<ButtonStateChange>::type mButtonStates;
+		Queue<DoubleClick>::type mDoubleClicks;
 		Queue<InputCommandType>::type mInputCommands;
 		Queue<InputCommandType>::type mInputCommands;
 		OSPositionalInputButtonStates mMouseMoveBtnState;
 		OSPositionalInputButtonStates mMouseMoveBtnState;
 
 
@@ -59,6 +67,7 @@ namespace CamelotFramework
 		boost::signals::connection mCursorMovedConn;
 		boost::signals::connection mCursorMovedConn;
 		boost::signals::connection mCursorPressedConn;
 		boost::signals::connection mCursorPressedConn;
 		boost::signals::connection mCursorReleasedConn;
 		boost::signals::connection mCursorReleasedConn;
+		boost::signals::connection mCursorDoubleClickConn;
 		boost::signals::connection mInputCommandConn;
 		boost::signals::connection mInputCommandConn;
 		boost::signals::connection mMouseWheelScrolledConn;
 		boost::signals::connection mMouseWheelScrolledConn;
 
 
@@ -82,6 +91,11 @@ namespace CamelotFramework
 		 */
 		 */
 		void cursorReleased(const Int2& cursorPos, OSMouseButton button, OSPositionalInputButtonStates& btnStates);
 		void cursorReleased(const Int2& cursorPos, OSMouseButton button, OSPositionalInputButtonStates& btnStates);
 
 
+		/**
+		 * @brief	Called from the message loop.
+		 */
+		void cursorDoubleClick(const Int2& cursorPos, OSPositionalInputButtonStates& btnStates);
+
 		/**
 		/**
 		 * @brief	Called from the message loop.
 		 * @brief	Called from the message loop.
 		 */
 		 */

+ 1 - 1
CamelotCore/Include/CmRenderWindow.h

@@ -46,7 +46,7 @@ namespace CamelotFramework
 			, vsync(false), vsyncInterval(1), hidden(false)
 			, vsync(false), vsyncInterval(1), hidden(false)
 			, displayFrequency(60), colorDepth(32), depthBuffer(true)
 			, displayFrequency(60), colorDepth(32), depthBuffer(true)
 			, FSAA(0), FSAAHint(""), gamma(false), left(-1), top(-1)
 			, FSAA(0), FSAAHint(""), gamma(false), left(-1), top(-1)
-			, title(""), border(WindowBorder::Normal), outerDimensions(false), enableDoubleClick(false)
+			, title(""), border(WindowBorder::Normal), outerDimensions(false), enableDoubleClick(true)
 			, monitorIndex(-1), toolWindow(false)
 			, monitorIndex(-1), toolWindow(false)
 		{ }
 		{ }
 
 

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

@@ -188,6 +188,7 @@ namespace CamelotFramework
 		static boost::signal<void(const Int2&, OSPositionalInputButtonStates)> onCursorMoved;
 		static boost::signal<void(const Int2&, OSPositionalInputButtonStates)> onCursorMoved;
 		static boost::signal<void(const Int2&, OSMouseButton button, OSPositionalInputButtonStates)> onCursorButtonPressed;
 		static boost::signal<void(const Int2&, OSMouseButton button, OSPositionalInputButtonStates)> onCursorButtonPressed;
 		static boost::signal<void(const Int2&, OSMouseButton button, OSPositionalInputButtonStates)> onCursorButtonReleased;
 		static boost::signal<void(const Int2&, OSMouseButton button, OSPositionalInputButtonStates)> onCursorButtonReleased;
+		static boost::signal<void(const Int2&, OSPositionalInputButtonStates)> onCursorDoubleClick;
 		static boost::signal<void(InputCommandType)> onInputCommand;
 		static boost::signal<void(InputCommandType)> onInputCommand;
 		static boost::signal<void(float)> onMouseWheelScrolled;
 		static boost::signal<void(float)> onMouseWheelScrolled;
 		static boost::signal<void(UINT32)> onCharInput;
 		static boost::signal<void(UINT32)> onCharInput;

+ 7 - 0
CamelotCore/Source/CmInput.cpp

@@ -35,6 +35,7 @@ namespace CamelotFramework
 		mOSInputHandler->onCursorMoved.connect(boost::bind(&Input::cursorMoved, this, _1));
 		mOSInputHandler->onCursorMoved.connect(boost::bind(&Input::cursorMoved, this, _1));
 		mOSInputHandler->onCursorPressed.connect(boost::bind(&Input::cursorPressed, this, _1));
 		mOSInputHandler->onCursorPressed.connect(boost::bind(&Input::cursorPressed, this, _1));
 		mOSInputHandler->onCursorReleased.connect(boost::bind(&Input::cursorReleased, this, _1));
 		mOSInputHandler->onCursorReleased.connect(boost::bind(&Input::cursorReleased, this, _1));
+		mOSInputHandler->onDoubleClick.connect(boost::bind(&Input::cursorDoubleClick, this, _1));
 		mOSInputHandler->onInputCommand.connect(boost::bind(&Input::inputCommandEntered, this, _1));
 		mOSInputHandler->onInputCommand.connect(boost::bind(&Input::inputCommandEntered, this, _1));
 
 
 		RenderWindowManager::instance().onFocusGained.connect(boost::bind(&Input::inputWindowChanged, this, _1));
 		RenderWindowManager::instance().onFocusGained.connect(boost::bind(&Input::inputWindowChanged, this, _1));
@@ -151,6 +152,12 @@ namespace CamelotFramework
 			onCursorReleased(event);
 			onCursorReleased(event);
 	}
 	}
 
 
+	void Input::cursorDoubleClick(const PositionalInputEvent& event)
+	{
+		if(!onDoubleClick.empty())
+			onDoubleClick(event);
+	}
+
 	void Input::inputCommandEntered(InputCommandType commandType)
 	void Input::inputCommandEntered(InputCommandType commandType)
 	{
 	{
 		if(!onInputCommand.empty())
 		if(!onInputCommand.empty())

+ 40 - 0
CamelotCore/Source/CmOSInputHandler.cpp

@@ -12,6 +12,7 @@ namespace CamelotFramework
 		mCursorMovedConn = Platform::onCursorMoved.connect(boost::bind(&OSInputHandler::cursorMoved, this, _1, _2));
 		mCursorMovedConn = Platform::onCursorMoved.connect(boost::bind(&OSInputHandler::cursorMoved, this, _1, _2));
 		mCursorPressedConn = Platform::onCursorButtonPressed.connect(boost::bind(&OSInputHandler::cursorPressed, this, _1, _2, _3));
 		mCursorPressedConn = Platform::onCursorButtonPressed.connect(boost::bind(&OSInputHandler::cursorPressed, this, _1, _2, _3));
 		mCursorReleasedConn = Platform::onCursorButtonReleased.connect(boost::bind(&OSInputHandler::cursorReleased, this, _1, _2, _3));
 		mCursorReleasedConn = Platform::onCursorButtonReleased.connect(boost::bind(&OSInputHandler::cursorReleased, this, _1, _2, _3));
+		mCursorDoubleClickConn = Platform::onCursorDoubleClick.connect(boost::bind(&OSInputHandler::cursorDoubleClick, this, _1, _2));
 		mInputCommandConn = Platform::onInputCommand.connect(boost::bind(&OSInputHandler::inputCommandEntered, this, _1));
 		mInputCommandConn = Platform::onInputCommand.connect(boost::bind(&OSInputHandler::inputCommandEntered, this, _1));
 
 
 		mMouseWheelScrolledConn  = Platform::onMouseWheelScrolled.connect(boost::bind(&OSInputHandler::mouseWheelScrolled, this, _1));
 		mMouseWheelScrolledConn  = Platform::onMouseWheelScrolled.connect(boost::bind(&OSInputHandler::mouseWheelScrolled, this, _1));
@@ -23,6 +24,7 @@ namespace CamelotFramework
 		mCursorMovedConn.disconnect();
 		mCursorMovedConn.disconnect();
 		mCursorPressedConn.disconnect();
 		mCursorPressedConn.disconnect();
 		mCursorReleasedConn.disconnect();
 		mCursorReleasedConn.disconnect();
+		mCursorDoubleClickConn.disconnect();
 		mInputCommandConn.disconnect();
 		mInputCommandConn.disconnect();
 		mMouseWheelScrolledConn.disconnect();
 		mMouseWheelScrolledConn.disconnect();
 	}
 	}
@@ -34,6 +36,7 @@ namespace CamelotFramework
 		float mouseScroll;
 		float mouseScroll;
 		OSPositionalInputButtonStates mouseMoveBtnState;
 		OSPositionalInputButtonStates mouseMoveBtnState;
 		Queue<ButtonStateChange>::type buttonStates;
 		Queue<ButtonStateChange>::type buttonStates;
+		Queue<DoubleClick>::type doubleClicks;
 		Queue<InputCommandType>::type inputCommands;
 		Queue<InputCommandType>::type inputCommands;
 
 
 		{
 		{
@@ -52,6 +55,9 @@ namespace CamelotFramework
 
 
 			inputCommands = mInputCommands;
 			inputCommands = mInputCommands;
 			mInputCommands = Queue<InputCommandType>::type();
 			mInputCommands = Queue<InputCommandType>::type();
+
+			doubleClicks = mDoubleClicks;
+			mDoubleClicks = Queue<DoubleClick>::type();
 		}
 		}
 
 
 		if(mousePosition != mLastCursorPos || (Math::Abs(mouseScroll) > 0.00001f))
 		if(mousePosition != mLastCursorPos || (Math::Abs(mouseScroll) > 0.00001f))
@@ -121,6 +127,29 @@ namespace CamelotFramework
 			buttonStates.pop();
 			buttonStates.pop();
 		}
 		}
 
 
+		while(!doubleClicks.empty())
+		{
+			if(!onDoubleClick.empty())
+			{
+				DoubleClick& btnState = doubleClicks.front();
+
+				PositionalInputEvent event;
+				event.alt = false;
+				event.shift = btnState.btnStates.shift;
+				event.control = btnState.btnStates.ctrl;
+				event.buttonStates[0] = btnState.btnStates.mouseButtons[0];
+				event.buttonStates[1] = btnState.btnStates.mouseButtons[1];
+				event.buttonStates[2] = btnState.btnStates.mouseButtons[2];
+				event.button = PositionalInputEventButton::Left;
+				event.screenPos = btnState.cursorPos;
+				event.type = PositionalInputEventType::DoubleClick;
+
+				onDoubleClick(event);
+			}
+
+			doubleClicks.pop();
+		}
+
 		while(!inputCommands.empty())
 		while(!inputCommands.empty())
 		{
 		{
 			if(!onInputCommand.empty())
 			if(!onInputCommand.empty())
@@ -181,6 +210,17 @@ namespace CamelotFramework
 		btnState.btnStates = btnStates;
 		btnState.btnStates = btnStates;
 	}
 	}
 
 
+	void OSInputHandler::cursorDoubleClick(const Int2& cursorPos, OSPositionalInputButtonStates& btnStates)
+	{
+		CM_LOCK_MUTEX(mOSInputMutex);
+
+		mDoubleClicks.push(DoubleClick());
+		DoubleClick& btnState = mDoubleClicks.back();
+
+		btnState.cursorPos = cursorPos;
+		btnState.btnStates = btnStates;
+	}
+
 	void OSInputHandler::inputCommandEntered(InputCommandType commandType)
 	void OSInputHandler::inputCommandEntered(InputCommandType commandType)
 	{
 	{
 		CM_LOCK_MUTEX(mOSInputMutex);
 		CM_LOCK_MUTEX(mOSInputMutex);

+ 11 - 0
CamelotCore/Source/CmPlatformWndProc.cpp

@@ -214,6 +214,17 @@ namespace CamelotFramework
 					onCursorButtonPressed(intMousePos, OSMouseButton::Right, btnStates);
 					onCursorButtonPressed(intMousePos, OSMouseButton::Right, btnStates);
 			}
 			}
 			break;
 			break;
+		case WM_LBUTTONDBLCLK:
+			{
+				Int2 intMousePos;
+				OSPositionalInputButtonStates btnStates;
+
+				getMouseData(hWnd, wParam, lParam, intMousePos, btnStates);
+
+				if(!onCursorDoubleClick.empty())
+					onCursorDoubleClick(intMousePos, btnStates);
+			}
+			break;
 		case WM_NCMOUSEMOVE:
 		case WM_NCMOUSEMOVE:
 		case WM_MOUSEMOVE:
 		case WM_MOUSEMOVE:
 			{
 			{

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

@@ -11,6 +11,7 @@ namespace CamelotFramework
 	boost::signal<void(const Int2&, OSPositionalInputButtonStates)> Platform::onCursorMoved;
 	boost::signal<void(const Int2&, OSPositionalInputButtonStates)> Platform::onCursorMoved;
 	boost::signal<void(const Int2&, OSMouseButton button, OSPositionalInputButtonStates)> Platform::onCursorButtonPressed;
 	boost::signal<void(const Int2&, OSMouseButton button, OSPositionalInputButtonStates)> Platform::onCursorButtonPressed;
 	boost::signal<void(const Int2&, OSMouseButton button, OSPositionalInputButtonStates)> Platform::onCursorButtonReleased;
 	boost::signal<void(const Int2&, OSMouseButton button, OSPositionalInputButtonStates)> Platform::onCursorButtonReleased;
+	boost::signal<void(const Int2&, OSPositionalInputButtonStates)> Platform::onCursorDoubleClick;
 	boost::signal<void(InputCommandType)> Platform::onInputCommand;
 	boost::signal<void(InputCommandType)> Platform::onInputCommand;
 	boost::signal<void(float)> Platform::onMouseWheelScrolled;
 	boost::signal<void(float)> Platform::onMouseWheelScrolled;
 	boost::signal<void(UINT32)> Platform::onCharInput;
 	boost::signal<void(UINT32)> Platform::onCharInput;

+ 0 - 16
TODO.txt

@@ -80,24 +80,10 @@ Longterm plans:
 	 - In inspector they can be expanded as children of the main resource, but cannot be directly modified?
 	 - In inspector they can be expanded as children of the main resource, but cannot be directly modified?
 	 - Deleting the main resource deletes the children too
 	 - Deleting the main resource deletes the children too
 
 
-<<Window controls>>
- - Create GUIWindowFrame/GUITitleBar control that can be added to the widget
-   - it has onMaximize, onMinimize, onClose, onMove, onResize callbacks
-    - when window is floating onMove/onResize will resize the actual Window
-    - when docked (EditorWindow should know if it's docked or not) it will call back the layout manager
-   - (Frame and title bar need to be separate as some windows will used tabbed bar instead of normal title bar)
- - GUITabbedArea that will serve for tabbed EditorWindows
-   - GUITabArea allows you to move tabs around, but also dock/undock them if the user drags the tab outside.
- - MainEditorWindow - handled specially, no need for a good interface
-   - Keeps a title bar, status bar and a layout manager (i.e. docking manager)
-
 <<MessageBox class>>
 <<MessageBox class>>
  - A good first test case for my GUI windows
  - A good first test case for my GUI windows
 
 
 <<Other>>
 <<Other>>
- - GUIManager events
- - GUIElement and GUIWidget "hasFocus" flag
-   - Only elements in focus receive keyboard events
  - There is an issue that custom-UIs won't have their mesh shared. For example most game UIs will be advanced and will 
  - There is an issue that custom-UIs won't have their mesh shared. For example most game UIs will be advanced and will 
    likely use on GUIWidget per element. However currently I only perform batching within a single widget which 
    likely use on GUIWidget per element. However currently I only perform batching within a single widget which 
    doesn't help in the mentioned case.
    doesn't help in the mentioned case.
@@ -180,7 +166,6 @@ Low priority TODO
  - Shared GPU buffers
  - Shared GPU buffers
    - wouldn't work atm due to the way I update the buffers (and the way I mark them dirty)
    - wouldn't work atm due to the way I update the buffers (and the way I mark them dirty)
    - Material::setParamBlock is commented out
    - Material::setParamBlock is commented out
- - Add support for diacritical marks to the input system (in WindowEventUtilities)
  - onMovedOrResized is still used by Viewport while that same callback is offered by RenderWindowManager. There is no need to have them in both places.
  - onMovedOrResized is still used by Viewport while that same callback is offered by RenderWindowManager. There is no need to have them in both places.
  - Texture "ScaleToFit" will cause the texture to repeat instead of clipping the image. e.g. a 50x20 texture placed on an 50x100 area will repeat 5x
  - Texture "ScaleToFit" will cause the texture to repeat instead of clipping the image. e.g. a 50x20 texture placed on an 50x100 area will repeat 5x
 ----------------------------------------------------------------------------------------------
 ----------------------------------------------------------------------------------------------
@@ -217,7 +202,6 @@ After polish and ideas:
  - Go to Game Engine Architecture book and make a list of Utility systems we will need (Config files, Parsers, File I/O etc)
  - Go to Game Engine Architecture book and make a list of Utility systems we will need (Config files, Parsers, File I/O etc)
  - Go to GEA book and read about resource managers before implementing them
  - Go to GEA book and read about resource managers before implementing them
    - Actually I should re-read most of the chapers in the book, or all of it
    - Actually I should re-read most of the chapers in the book, or all of it
- - When building internal GUI make sure to use CEGUI as a reference
  - OpenGL non-Win32 window files haven't been properly parsed or tested
  - OpenGL non-Win32 window files haven't been properly parsed or tested
    - Since I probably can't compile them, try adding them to VS and see what intellisense says?
    - Since I probably can't compile them, try adding them to VS and see what intellisense says?
  - Textures and all other buffers keep a copy of their data in system memory. If there are memory constraints we might need a way to avoid this.
  - Textures and all other buffers keep a copy of their data in system memory. If there are memory constraints we might need a way to avoid this.