1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-12 02:28:11 +02:00

Merge pull request #2769 from IvanSavenko/touch_tolerance

(1.3.2) Implemented tolerance for touch events
This commit is contained in:
Ivan Savenko 2023-09-08 16:45:04 +03:00 committed by GitHub
commit fccd564d8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 99 additions and 25 deletions

View File

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

View File

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

View File

@ -78,6 +78,9 @@ struct TouchInputParameters
/// gesture will be qualified as pinch if distance between fingers is at least specified here /// gesture will be qualified as pinch if distance between fingers is at least specified here
uint32_t pinchSensitivityThreshold = 10; 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 useRelativeMode = false;
bool hapticFeedbackEnabled = false; bool hapticFeedbackEnabled = false;

View File

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

View File

@ -109,6 +109,8 @@ public:
/// by default, usedEvents inside UI elements are always handled /// by default, usedEvents inside UI elements are always handled
bool receiveEvent(const Point & position, int eventType) const override; 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 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(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 const Rect & center(bool propagate = true); //centers when pos.w and pos.h are set, returns new position

View File

@ -16,7 +16,7 @@
#include "MouseButton.h" #include "MouseButton.h"
#include "WindowHandler.h" #include "WindowHandler.h"
#include "../../lib/Point.h" #include "../../lib/Rect.h"
template<typename Functor> template<typename Functor>
void EventDispatcher::processLists(ui16 activityFlag, const Functor & cb) void EventDispatcher::processLists(ui16 activityFlag, const Functor & cb)
@ -134,28 +134,64 @@ void EventDispatcher::dispatchMouseDoubleClick(const Point & position)
} }
if(!doubleClicked) 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; auto hlp = rclickable;
for(auto & i : hlp) for(auto & i : hlp)
{ {
if(!vstd::contains(rclickable, i)) if(!vstd::contains(rclickable, i))
continue; continue;
if( !i->receiveEvent(position, AEventsReceiver::LCLICK)) if( !i->receiveEvent(position, AEventsReceiver::SHOW_POPUP) && i != nearestElement)
continue; continue;
i->showPopupWindow(position); i->showPopupWindow(position);
@ -170,7 +206,7 @@ void EventDispatcher::dispatchClosePopup(const Point & position)
assert(!GH.windows().isTopWindowPopup()); 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 // WARNING: this approach is NOT SAFE
// 1) We allow (un)registering elements when list itself is being processed/iterated // 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(!) // 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 // 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 // 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; auto hlp = lclickable;
for(auto & i : hlp) for(auto & i : hlp)
{ {
if(!vstd::contains(lclickable, i)) if(!vstd::contains(lclickable, i))
continue; continue;
if( i->receiveEvent(position, AEventsReceiver::LCLICK)) if( i->receiveEvent(position, AEventsReceiver::LCLICK) || i == nearestElement)
{ {
if(isPressed) if(isPressed)
i->clickPressed(position); i->clickPressed(position);

View File

@ -35,8 +35,8 @@ class EventDispatcher
EventReceiversList textInterested; EventReceiversList textInterested;
EventReceiversList panningInterested; 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> template<typename Functor>
void processLists(ui16 activityFlag, const Functor & cb); void processLists(ui16 activityFlag, const Functor & cb);
@ -56,15 +56,15 @@ public:
void dispatchShortcutReleased(const std::vector<EShortcut> & shortcuts); void dispatchShortcutReleased(const std::vector<EShortcut> & shortcuts);
/// Mouse events /// Mouse events
void dispatchMouseLeftButtonPressed(const Point & position); void dispatchMouseLeftButtonPressed(const Point & position, int tolerance);
void dispatchMouseLeftButtonReleased(const Point & position); void dispatchMouseLeftButtonReleased(const Point & position, int tolerance);
void dispatchMouseScrolled(const Point & distance, const Point & position); void dispatchMouseScrolled(const Point & distance, const Point & position);
void dispatchMouseDoubleClick(const Point & position); void dispatchMouseDoubleClick(const Point & position);
void dispatchMouseMoved(const Point & distance, const Point & position); void dispatchMouseMoved(const Point & distance, const Point & position);
void dispatchMouseDragged(const Point & currentPosition, const Point & lastUpdateDistance); 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 dispatchClosePopup(const Point & position);
void dispatchGesturePanningStarted(const Point & initialPosition); void dispatchGesturePanningStarted(const Point & initialPosition);

View File

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

View File

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

View File

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

View File

@ -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 VCMI_LIB_NAMESPACE_END

View File

@ -142,6 +142,9 @@ public:
return x == other.x && y == other.y && w == other.w && h == other.h; 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 /// returns true if this rect intersects with another rect
DLL_LINKAGE bool intersectionTest(const Rect & other) const; DLL_LINKAGE bool intersectionTest(const Rect & other) const;