Pārlūkot izejas kodu

Merge pull request #2769 from IvanSavenko/touch_tolerance

(1.3.2) Implemented tolerance for touch events
Ivan Savenko 2 gadi atpakaļ
vecāks
revīzija
fccd564d8d

+ 3 - 3
client/eventsSDL/InputSourceMouse.cpp

@@ -50,10 +50,10 @@ void InputSourceMouse::handleEventMouseButtonDown(const SDL_MouseButtonEvent & b
 			if(button.clicks > 1)
 				GH.events().dispatchMouseDoubleClick(position);
 			else
-				GH.events().dispatchMouseLeftButtonPressed(position);
+				GH.events().dispatchMouseLeftButtonPressed(position, 0);
 			break;
 		case SDL_BUTTON_RIGHT:
-			GH.events().dispatchShowPopup(position);
+			GH.events().dispatchShowPopup(position, 0);
 			break;
 		case SDL_BUTTON_MIDDLE:
 			middleClickPosition = position;
@@ -74,7 +74,7 @@ void InputSourceMouse::handleEventMouseButtonUp(const SDL_MouseButtonEvent & but
 	switch(button.button)
 	{
 		case SDL_BUTTON_LEFT:
-			GH.events().dispatchMouseLeftButtonReleased(position);
+			GH.events().dispatchMouseLeftButtonReleased(position, 0);
 			break;
 		case SDL_BUTTON_RIGHT:
 			GH.events().dispatchClosePopup(position);

+ 7 - 6
client/eventsSDL/InputSourceTouch.cpp

@@ -39,6 +39,7 @@ InputSourceTouch::InputSourceTouch()
 	params.relativeModeSpeedFactor = settings["general"]["relativePointerSpeedMultiplier"].Float();
 	params.longTouchTimeMilliseconds = settings["general"]["longTouchTimeMilliseconds"].Float();
 	params.hapticFeedbackEnabled = settings["general"]["hapticFeedback"].Bool();
+	params.touchToleranceDistance = settings["input"]["touchToleranceDistance"].Bool();
 
 	if (params.useRelativeMode)
 		state = TouchState::RELATIVE_MODE;
@@ -121,9 +122,9 @@ void InputSourceTouch::handleEventFingerDown(const SDL_TouchFingerEvent & tfinge
 			if(tfinger.x > 0.5)
 			{
 				if (tfinger.y < 0.5)
-					GH.events().dispatchShowPopup(GH.getCursorPosition());
+					GH.events().dispatchShowPopup(GH.getCursorPosition(), params.touchToleranceDistance);
 				else
-					GH.events().dispatchMouseLeftButtonPressed(GH.getCursorPosition());
+					GH.events().dispatchMouseLeftButtonPressed(GH.getCursorPosition(), params.touchToleranceDistance);
 			}
 			break;
 		}
@@ -168,7 +169,7 @@ void InputSourceTouch::handleEventFingerUp(const SDL_TouchFingerEvent & tfinger)
 				if (tfinger.y < 0.5)
 					GH.events().dispatchClosePopup(GH.getCursorPosition());
 				else
-					GH.events().dispatchMouseLeftButtonReleased(GH.getCursorPosition());
+					GH.events().dispatchMouseLeftButtonReleased(GH.getCursorPosition(), params.touchToleranceDistance);
 			}
 			break;
 		}
@@ -180,8 +181,8 @@ void InputSourceTouch::handleEventFingerUp(const SDL_TouchFingerEvent & tfinger)
 		case TouchState::TAP_DOWN_SHORT:
 		{
 			GH.input().setCursorPosition(convertTouchToMouse(tfinger));
-			GH.events().dispatchMouseLeftButtonPressed(convertTouchToMouse(tfinger));
-			GH.events().dispatchMouseLeftButtonReleased(convertTouchToMouse(tfinger));
+			GH.events().dispatchMouseLeftButtonPressed(convertTouchToMouse(tfinger), params.touchToleranceDistance);
+			GH.events().dispatchMouseLeftButtonReleased(convertTouchToMouse(tfinger), params.touchToleranceDistance);
 			state = TouchState::IDLE;
 			break;
 		}
@@ -230,7 +231,7 @@ void InputSourceTouch::handleUpdate()
 		uint32_t currentTime = SDL_GetTicks();
 		if (currentTime > lastTapTimeTicks + params.longTouchTimeMilliseconds)
 		{
-			GH.events().dispatchShowPopup(GH.getCursorPosition());
+			GH.events().dispatchShowPopup(GH.getCursorPosition(), params.touchToleranceDistance);
 
 			if (GH.windows().isTopWindowPopup())
 			{

+ 3 - 0
client/eventsSDL/InputSourceTouch.h

@@ -78,6 +78,9 @@ struct TouchInputParameters
 	/// gesture will be qualified as pinch if distance between fingers is at least specified here
 	uint32_t pinchSensitivityThreshold = 10;
 
+	/// touch event will trigger clicking of elements up to X pixels away from actual touch position
+	uint32_t touchToleranceDistance = 20;
+
 	bool useRelativeMode = false;
 
 	bool hapticFeedbackEnabled = false;

+ 5 - 0
client/gui/CIntObject.cpp

@@ -263,6 +263,11 @@ bool CIntObject::receiveEvent(const Point & position, int eventType) const
 	return pos.isInside(position);
 }
 
+const Rect & CIntObject::getPosition() const
+{
+	return pos;
+}
+
 void CIntObject::onScreenResize()
 {
 	center(pos, true);

+ 2 - 0
client/gui/CIntObject.h

@@ -109,6 +109,8 @@ public:
 	/// by default, usedEvents inside UI elements are always handled
 	bool receiveEvent(const Point & position, int eventType) const override;
 
+	const Rect & getPosition() const override;
+
 	const Rect & center(const Rect &r, bool propagate = true); //sets pos so that r will be in the center of screen, assigns sizes of r to pos, returns new position
 	const Rect & center(const Point &p, bool propagate = true);  //moves object so that point p will be in its center
 	const Rect & center(bool propagate = true); //centers when pos.w and pos.h are set, returns new position

+ 48 - 10
client/gui/EventDispatcher.cpp

@@ -16,7 +16,7 @@
 #include "MouseButton.h"
 #include "WindowHandler.h"
 
-#include "../../lib/Point.h"
+#include "../../lib/Rect.h"
 
 template<typename Functor>
 void EventDispatcher::processLists(ui16 activityFlag, const Functor & cb)
@@ -134,28 +134,64 @@ void EventDispatcher::dispatchMouseDoubleClick(const Point & position)
 	}
 
 	if(!doubleClicked)
-		handleLeftButtonClick(position, true);
+		handleLeftButtonClick(position, 0, true);
 }
 
-void EventDispatcher::dispatchMouseLeftButtonPressed(const Point & position)
+void EventDispatcher::dispatchMouseLeftButtonPressed(const Point & position, int tolerance)
 {
-	handleLeftButtonClick(position, true);
+	handleLeftButtonClick(position, tolerance, true);
 }
 
-void EventDispatcher::dispatchMouseLeftButtonReleased(const Point & position)
+void EventDispatcher::dispatchMouseLeftButtonReleased(const Point & position, int tolerance)
 {
-	handleLeftButtonClick(position, false);
+	handleLeftButtonClick(position, tolerance, false);
 }
 
-void EventDispatcher::dispatchShowPopup(const Point & position)
+AEventsReceiver * EventDispatcher::findElementInToleranceRange(const EventReceiversList & list, const Point & position, int eventToTest, int tolerance)
 {
+	AEventsReceiver * bestElement = nullptr;
+	int bestDistance = std::numeric_limits<int>::max();
+
+	for(auto & i : list)
+	{
+		// if there is element that can actually receive event then tolerance clicking is disabled
+		if( i->receiveEvent(position, eventToTest))
+			return nullptr;
+
+		if (i->getPosition().distanceTo(position) > bestDistance)
+			continue;
+
+		Point center = i->getPosition().center();
+		Point distance = center - position;
+
+		if (distance.lengthSquared() == 0)
+			continue;
+
+		Point moveDelta = distance * tolerance / distance.length();
+		Point testPosition = position + moveDelta;
+
+		if( !i->receiveEvent(testPosition, eventToTest))
+			continue;
+
+		bestElement = i;
+		bestDistance = i->getPosition().distanceTo(position);
+	}
+
+	return bestElement;
+}
+
+void EventDispatcher::dispatchShowPopup(const Point & position, int tolerance)
+{
+	AEventsReceiver * nearestElement = findElementInToleranceRange(rclickable, position, AEventsReceiver::LCLICK, tolerance);
+
 	auto hlp = rclickable;
+
 	for(auto & i : hlp)
 	{
 		if(!vstd::contains(rclickable, i))
 			continue;
 
-		if( !i->receiveEvent(position, AEventsReceiver::LCLICK))
+		if( !i->receiveEvent(position, AEventsReceiver::SHOW_POPUP) && i != nearestElement)
 			continue;
 
 		i->showPopupWindow(position);
@@ -170,7 +206,7 @@ void EventDispatcher::dispatchClosePopup(const Point & position)
 	assert(!GH.windows().isTopWindowPopup());
 }
 
-void EventDispatcher::handleLeftButtonClick(const Point & position, bool isPressed)
+void EventDispatcher::handleLeftButtonClick(const Point & position, int tolerance, bool isPressed)
 {
 	// WARNING: this approach is NOT SAFE
 	// 1) We allow (un)registering elements when list itself is being processed/iterated
@@ -181,13 +217,15 @@ void EventDispatcher::handleLeftButtonClick(const Point & position, bool isPress
 	// 3) new element is created *with exactly same address(!)
 	// 4) new element is registered and code will incorrectly assume that this element is still registered
 	// POSSIBLE SOLUTION: make EventReceivers inherit from create_shared_from this and store weak_ptr's in lists
+	AEventsReceiver * nearestElement = findElementInToleranceRange(lclickable, position, AEventsReceiver::LCLICK, tolerance);
 	auto hlp = lclickable;
+
 	for(auto & i : hlp)
 	{
 		if(!vstd::contains(lclickable, i))
 			continue;
 
-		if( i->receiveEvent(position, AEventsReceiver::LCLICK))
+		if( i->receiveEvent(position, AEventsReceiver::LCLICK) || i == nearestElement)
 		{
 			if(isPressed)
 				i->clickPressed(position);

+ 5 - 5
client/gui/EventDispatcher.h

@@ -35,8 +35,8 @@ class EventDispatcher
 	EventReceiversList textInterested;
 	EventReceiversList panningInterested;
 
-	void handleLeftButtonClick(const Point & position, bool isPressed);
-
+	void handleLeftButtonClick(const Point & position, int tolerance, bool isPressed);
+	AEventsReceiver * findElementInToleranceRange(const EventReceiversList & list, const Point & position, int eventToTest, int tolerance);
 
 	template<typename Functor>
 	void processLists(ui16 activityFlag, const Functor & cb);
@@ -56,15 +56,15 @@ public:
 	void dispatchShortcutReleased(const std::vector<EShortcut> & shortcuts);
 
 	/// Mouse events
-	void dispatchMouseLeftButtonPressed(const Point & position);
-	void dispatchMouseLeftButtonReleased(const Point & position);
+	void dispatchMouseLeftButtonPressed(const Point & position, int tolerance);
+	void dispatchMouseLeftButtonReleased(const Point & position, int tolerance);
 	void dispatchMouseScrolled(const Point & distance, const Point & position);
 	void dispatchMouseDoubleClick(const Point & position);
 	void dispatchMouseMoved(const Point & distance, const Point & position);
 
 	void dispatchMouseDragged(const Point & currentPosition, const Point & lastUpdateDistance);
 
-	void dispatchShowPopup(const Point & position);
+	void dispatchShowPopup(const Point & position, int tolerance);
 	void dispatchClosePopup(const Point & position);
 
 	void dispatchGesturePanningStarted(const Point & initialPosition);

+ 3 - 0
client/gui/EventsReceiver.h

@@ -11,6 +11,7 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 class Point;
+class Rect;
 VCMI_LIB_NAMESPACE_END
 
 class EventDispatcher;
@@ -39,6 +40,8 @@ protected:
 	/// If true, event of selected type in selected position will be processed by this element
 	virtual bool receiveEvent(const Point & position, int eventType) const= 0;
 
+	virtual const Rect & getPosition() const= 0;
+
 public:
 	virtual void clickPressed(const Point & cursorPosition) {}
 	virtual void clickReleased(const Point & cursorPosition) {}

+ 7 - 0
client/widgets/Buttons.cpp

@@ -128,6 +128,13 @@ void CButton::setState(ButtonState newState)
 {
 	if (state == newState)
 		return;
+
+	if (newState == BLOCKED)
+		removeUsedEvents(LCLICK | SHOW_POPUP | HOVER | KEYBOARD);
+	else
+		addUsedEvents(LCLICK | SHOW_POPUP | HOVER | KEYBOARD);
+
+
 	state = newState;
 	update();
 }

+ 5 - 1
config/schemas/settings.json

@@ -214,11 +214,15 @@
 			"type" : "object",
 			"additionalProperties" : false,
 			"default" : {},
-			"required" : [ "radialWheelGarrisonSwipe" ],
+			"required" : [ "radialWheelGarrisonSwipe", "touchToleranceDistance" ],
 			"properties" : {
 				"radialWheelGarrisonSwipe" : {
 					"type" : "boolean",
 					"default" : true
+				},
+				"touchToleranceDistance" : {
+					"type" : "number",
+					"default" : 20
 				}
 			}
 		},

+ 8 - 0
lib/Rect.cpp

@@ -136,4 +136,12 @@ Rect Rect::intersect(const Rect & other) const
 	}
 }
 
+int Rect::distanceTo(const Point & target) const
+{
+	int distanceX = std::max({left() - target.x, 0, target.x - right()});
+	int distanceY = std::max({top() - target.y, 0, target.y - bottom()});
+
+	return Point(distanceX, distanceY).length();
+}
+
 VCMI_LIB_NAMESPACE_END

+ 3 - 0
lib/Rect.h

@@ -142,6 +142,9 @@ public:
 		return x == other.x && y == other.y && w == other.w && h == other.h;
 	}
 
+	/// returns distance from this rect to point, or 0 if inside
+	DLL_LINKAGE int distanceTo(const Point & target) const;
+
 	/// returns true if this rect intersects with another rect
 	DLL_LINKAGE bool intersectionTest(const Rect & other) const;