diff --git a/client/eventsSDL/InputSourceMouse.cpp b/client/eventsSDL/InputSourceMouse.cpp index cf2b80785..ec971c645 100644 --- a/client/eventsSDL/InputSourceMouse.cpp +++ b/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); diff --git a/client/eventsSDL/InputSourceTouch.cpp b/client/eventsSDL/InputSourceTouch.cpp index 02e89015c..f90cb2d68 100644 --- a/client/eventsSDL/InputSourceTouch.cpp +++ b/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()) { diff --git a/client/eventsSDL/InputSourceTouch.h b/client/eventsSDL/InputSourceTouch.h index b82ae1688..56d112dc7 100644 --- a/client/eventsSDL/InputSourceTouch.h +++ b/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; diff --git a/client/gui/CIntObject.cpp b/client/gui/CIntObject.cpp index 93d0ebc70..74c946d16 100644 --- a/client/gui/CIntObject.cpp +++ b/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); diff --git a/client/gui/CIntObject.h b/client/gui/CIntObject.h index 4b4dbfd87..c1bddd586 100644 --- a/client/gui/CIntObject.h +++ b/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 diff --git a/client/gui/EventDispatcher.cpp b/client/gui/EventDispatcher.cpp index 068cbb8b3..36e5822f2 100644 --- a/client/gui/EventDispatcher.cpp +++ b/client/gui/EventDispatcher.cpp @@ -16,7 +16,7 @@ #include "MouseButton.h" #include "WindowHandler.h" -#include "../../lib/Point.h" +#include "../../lib/Rect.h" template 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::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); diff --git a/client/gui/EventDispatcher.h b/client/gui/EventDispatcher.h index c259b14e3..dee3d77cd 100644 --- a/client/gui/EventDispatcher.h +++ b/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 void processLists(ui16 activityFlag, const Functor & cb); @@ -56,15 +56,15 @@ public: void dispatchShortcutReleased(const std::vector & 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); diff --git a/client/gui/EventsReceiver.h b/client/gui/EventsReceiver.h index 37aeb5fa4..2206c70bf 100644 --- a/client/gui/EventsReceiver.h +++ b/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) {} diff --git a/client/widgets/Buttons.cpp b/client/widgets/Buttons.cpp index ae4737df7..b33bd8e6a 100644 --- a/client/widgets/Buttons.cpp +++ b/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(); } diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 9c20c3a00..4f588e1d6 100644 --- a/config/schemas/settings.json +++ b/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 } } }, diff --git a/lib/Rect.cpp b/lib/Rect.cpp index 4bc12dff2..1dfcd8fbc 100644 --- a/lib/Rect.cpp +++ b/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 diff --git a/lib/Rect.h b/lib/Rect.h index 6197a4c18..50d514844 100644 --- a/lib/Rect.h +++ b/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;