1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +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)
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);

View File

@ -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())
{

View File

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

View File

@ -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);

View File

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

View File

@ -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);

View File

@ -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);

View File

@ -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) {}

View File

@ -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();
}

View File

@ -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
}
}
},

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

View File

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