浏览代码

Implemented left & right click support for touch input

Ivan Savenko 2 年之前
父节点
当前提交
1a5c69a424
共有 3 个文件被更改,包括 219 次插入58 次删除
  1. 1 0
      client/eventsSDL/InputHandler.cpp
  2. 151 54
      client/eventsSDL/InputSourceTouch.cpp
  3. 67 4
      client/eventsSDL/InputSourceTouch.h

+ 1 - 0
client/eventsSDL/InputHandler.cpp

@@ -78,6 +78,7 @@ void InputHandler::processEvents()
 		handleCurrentEvent(currentEvent);
 
 	eventsQueue.clear();
+	fingerHandler->handleUpdate();
 }
 
 bool InputHandler::ignoreEventsUntilInput()

+ 151 - 54
client/eventsSDL/InputSourceTouch.cpp

@@ -20,94 +20,175 @@
 #include "../gui/MouseButton.h"
 
 #include <SDL_events.h>
-#include <SDL_render.h>
 #include <SDL_hints.h>
+#include <SDL_timer.h>
 
 InputSourceTouch::InputSourceTouch()
-	: multifinger(false)
-	, isPointerRelativeMode(settings["general"]["userRelativePointer"].Bool())
-	, pointerSpeedMultiplier(settings["general"]["relativePointerSpeedMultiplier"].Float())
+	: lastTapTimeTicks(0)
 {
-	if(isPointerRelativeMode)
-	{
-		SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
-		SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
-	}
+	params.useRelativeMode = settings["general"]["userRelativePointer"].Bool();
+	params.relativeModeSpeedFactor = settings["general"]["relativePointerSpeedMultiplier"].Float();
+
+	if (params.useRelativeMode)
+		state = TouchState::RELATIVE_MODE;
+	else
+		state = TouchState::IDLE;
+
+	SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
+	SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
 }
 
 void InputSourceTouch::handleEventFingerMotion(const SDL_TouchFingerEvent & tfinger)
 {
-	if(isPointerRelativeMode)
+	switch(state)
 	{
-		Point screenSize = GH.screenDimensions();
+		case TouchState::RELATIVE_MODE:
+		{
+			Point screenSize = GH.screenDimensions();
 
-		Point moveDistance {
-			static_cast<int>(screenSize.x * pointerSpeedMultiplier * tfinger.dx),
-			static_cast<int>(screenSize.y * pointerSpeedMultiplier * tfinger.dy)
-		};
+			Point moveDistance {
+				static_cast<int>(screenSize.x * params.relativeModeSpeedFactor * tfinger.dx),
+				static_cast<int>(screenSize.y * params.relativeModeSpeedFactor * tfinger.dy)
+			};
 
-		GH.input().moveCursorPosition(moveDistance);
+			GH.input().moveCursorPosition(moveDistance);
+			break;
+		}
+		case TouchState::IDLE:
+		{
+			// no-op, might happen in some edge cases, e.g. when fingerdown event was ignored
+			break;
+		}
+		case TouchState::TAP_DOWN_SHORT:
+		{
+			state = TouchState::TAP_DOWN_PANNING;
+			break;
+		}
+		case TouchState::TAP_DOWN_PANNING:
+		{
+			emitPanningEvent();
+			break;
+		}
+		case TouchState::TAP_DOWN_DOUBLE:
+		{
+			emitPinchEvent();
+			break;
+		}
+		case TouchState::TAP_DOWN_LONG:
+		{
+			// no-op
+			break;
+		}
 	}
 }
 
 void InputSourceTouch::handleEventFingerDown(const SDL_TouchFingerEvent & tfinger)
 {
-	if(isPointerRelativeMode)
+	lastTapTimeTicks = tfinger.timestamp;
+
+	switch(state)
 	{
-		if(tfinger.x > 0.5)
+		case TouchState::RELATIVE_MODE:
 		{
-			bool isRightClick = tfinger.y < 0.5;
-
-			if (isRightClick)
-				GH.events().dispatchMouseButtonPressed(MouseButton::RIGHT, GH.getCursorPosition());
-			else
-				GH.events().dispatchMouseButtonPressed(MouseButton::LEFT, GH.getCursorPosition());
+			if(tfinger.x > 0.5)
+			{
+				MouseButton button =  tfinger.y < 0.5 ? MouseButton::RIGHT : MouseButton::LEFT;
+				GH.events().dispatchMouseButtonPressed(button, GH.getCursorPosition());
+			}
+			break;
 		}
-	}
-#ifndef VCMI_IOS
-	else
-	{
-		auto fingerCount = SDL_GetNumTouchFingers(tfinger.touchId);
-		multifinger = fingerCount > 1;
-
-		if(fingerCount == 2)
+		case TouchState::IDLE:
 		{
-			Point position = convertTouchToMouse(tfinger);
-
-			GH.input().setCursorPosition(position);
-			GH.events().dispatchMouseButtonPressed(MouseButton::RIGHT, position);
+			GH.input().setCursorPosition(convertTouchToMouse(tfinger));
+			state = TouchState::TAP_DOWN_SHORT;
+			break;
+		}
+		case TouchState::TAP_DOWN_SHORT:
+		{
+			GH.input().setCursorPosition(convertTouchToMouse(tfinger));
+			state = TouchState::TAP_DOWN_DOUBLE;
+			break;
+		}
+		case TouchState::TAP_DOWN_PANNING:
+		{
+			GH.input().setCursorPosition(convertTouchToMouse(tfinger));
+			state = TouchState::TAP_DOWN_DOUBLE;
+			break;
+		}
+		case TouchState::TAP_DOWN_DOUBLE:
+		{
+			// TODO? ignore?
+			break;
+		}
+		case TouchState::TAP_DOWN_LONG:
+		{
+			// no-op
+			break;
 		}
 	}
-#endif //VCMI_IOS
 }
 
 void InputSourceTouch::handleEventFingerUp(const SDL_TouchFingerEvent & tfinger)
 {
-	if(isPointerRelativeMode)
+	switch(state)
 	{
-		if(tfinger.x > 0.5)
+		case TouchState::RELATIVE_MODE:
 		{
-			bool isRightClick = tfinger.y < 0.5;
-
-			if (isRightClick)
-				GH.events().dispatchMouseButtonReleased(MouseButton::RIGHT, GH.getCursorPosition());
-			else
-				GH.events().dispatchMouseButtonReleased(MouseButton::LEFT, GH.getCursorPosition());
+			if(tfinger.x > 0.5)
+			{
+				MouseButton button =  tfinger.y < 0.5 ? MouseButton::RIGHT : MouseButton::LEFT;
+				GH.events().dispatchMouseButtonReleased(button, GH.getCursorPosition());
+			}
+			break;
+		}
+		case TouchState::IDLE:
+		{
+			// no-op, might happen in some edge cases, e.g. when fingerdown event was ignored
+			break;
+		}
+		case TouchState::TAP_DOWN_SHORT:
+		{
+			GH.input().setCursorPosition(convertTouchToMouse(tfinger));
+			GH.events().dispatchMouseButtonPressed(MouseButton::LEFT, convertTouchToMouse(tfinger));
+			GH.events().dispatchMouseButtonReleased(MouseButton::LEFT, convertTouchToMouse(tfinger));
+			state = TouchState::IDLE;
+			break;
+		}
+		case TouchState::TAP_DOWN_PANNING:
+		{
+			state = TouchState::IDLE;
+			break;
+		}
+		case TouchState::TAP_DOWN_DOUBLE:
+		{
+			if (SDL_GetNumTouchFingers(tfinger.touchId) == 1)
+				state = TouchState::TAP_DOWN_PANNING;
+			break;
+		}
+		case TouchState::TAP_DOWN_LONG:
+		{
+			if (SDL_GetNumTouchFingers(tfinger.touchId) == 0)
+			{
+				GH.input().setCursorPosition(convertTouchToMouse(tfinger));
+				GH.events().dispatchMouseButtonReleased(MouseButton::RIGHT, convertTouchToMouse(tfinger));
+				state = TouchState::IDLE;
+			}
+			break;
 		}
 	}
-#ifndef VCMI_IOS
-	else
+}
+
+void InputSourceTouch::handleUpdate()
+{
+	if ( state == TouchState::TAP_DOWN_SHORT)
 	{
-		if(multifinger)
+		uint32_t currentTime = SDL_GetTicks();
+		if (currentTime > lastTapTimeTicks + params.longPressTimeMilliseconds)
 		{
-			auto fingerCount = SDL_GetNumTouchFingers(tfinger.touchId);
-			Point position = convertTouchToMouse(tfinger);
-			GH.input().setCursorPosition(position);
-			GH.events().dispatchMouseButtonReleased(MouseButton::RIGHT, position);
-			multifinger = fingerCount != 0;
+			state = TouchState::TAP_DOWN_LONG;
+			GH.events().dispatchMouseButtonPressed(MouseButton::RIGHT, GH.getCursorPosition());
 		}
 	}
-#endif //VCMI_IOS
 }
 
 Point InputSourceTouch::convertTouchToMouse(const SDL_TouchFingerEvent & tfinger)
@@ -117,5 +198,21 @@ Point InputSourceTouch::convertTouchToMouse(const SDL_TouchFingerEvent & tfinger
 
 bool InputSourceTouch::isMouseButtonPressed(MouseButton button) const
 {
+	if (state == TouchState::TAP_DOWN_LONG)
+	{
+		if (button == MouseButton::RIGHT)
+			return true;
+	}
+
 	return false;
 }
+
+void InputSourceTouch::emitPanningEvent()
+{
+	// TODO
+}
+
+void InputSourceTouch::emitPinchEvent()
+{
+	// TODO
+}

+ 67 - 4
client/eventsSDL/InputSourceTouch.h

@@ -17,16 +17,77 @@ VCMI_LIB_NAMESPACE_END
 enum class MouseButton;
 struct SDL_TouchFingerEvent;
 
+/// Enumeration that describes current state of gesture recognition
+enum class TouchState
+{
+	// special state that allows no transitions
+	// used when player selects "relative mode" in Launcher
+	// in this mode touchscreen acts like touchpad, moving cursor at certains speed
+	// and generates events for positions below cursor instead of positions below touch events
+	RELATIVE_MODE,
+
+	// no active touch events
+	// DOWN -> transition to TAP_DOWN_SHORT
+	// MOTION / UP -> not expected
+	IDLE,
+
+	// single finger is touching the screen for a short time
+	// DOWN -> transition to TAP_DOWN_DOUBLE
+	// MOTION -> transition to TAP_DOWN_PANNING
+	// UP -> transition to IDLE, emit onLeftClickDown and onLeftClickUp
+	// on timer -> transition to TAP_DOWN_LONG, emit onRightClickDown event
+	TAP_DOWN_SHORT,
+
+	// single finger is moving across screen
+	// DOWN -> transition to TAP_DOWN_DOUBLE
+	// MOTION -> emit panning event
+	// UP -> transition to IDLE
+	TAP_DOWN_PANNING,
+
+	// two fingers are touching the screen
+	// DOWN -> ??? how to handle 3rd finger? Ignore?
+	// MOTION -> emit pinch event
+	// UP -> transition to TAP_DOWN
+	TAP_DOWN_DOUBLE,
+
+	// single finger is down for long period of time
+	// DOWN -> ignored
+	// MOTION -> ignored
+	// UP -> transition to IDLE, generate onRightClickUp() event
+	TAP_DOWN_LONG,
+
+
+	// Possible transitions:
+	//                               -> DOUBLE
+	//                    -> PANNING -> IDLE
+	// IDLE -> DOWN_SHORT -> IDLE
+	//                    -> LONG -> IDLE
+	//                    -> DOUBLE -> PANNING
+	//                              -> IDLE
+};
+
+struct TouchInputParameters
+{
+	double relativeModeSpeedFactor = 1.0;
+	uint32_t longPressTimeMilliseconds = 500;
+
+	bool useHoldGesture = true;
+	bool usePanGesture = true;
+	bool usePinchGesture = true;
+	bool useRelativeMode = false;
+};
+
 /// Class that handles touchscreen input from SDL events
 class InputSourceTouch
 {
-	double pointerSpeedMultiplier;
-	bool multifinger;
-	bool isPointerRelativeMode;
+	TouchInputParameters params;
+	TouchState state;
+	uint32_t lastTapTimeTicks;
 
 	Point convertTouchToMouse(const SDL_TouchFingerEvent & current);
 
-	void fakeMouseButtonEventRelativeMode(bool down, bool right);
+	void emitPanningEvent();
+	void emitPinchEvent();
 
 public:
 	InputSourceTouch();
@@ -35,5 +96,7 @@ public:
 	void handleEventFingerDown(const SDL_TouchFingerEvent & current);
 	void handleEventFingerUp(const SDL_TouchFingerEvent & current);
 
+	void handleUpdate();
+
 	bool isMouseButtonPressed(MouseButton button) const;
 };