2023-05-18 19:32:29 +02:00
|
|
|
/*
|
|
|
|
* InputSourceTouch.cpp, part of VCMI engine
|
|
|
|
*
|
|
|
|
* Authors: listed in file AUTHORS in main folder
|
|
|
|
*
|
|
|
|
* License: GNU General Public License v2.0 or later
|
|
|
|
* Full text of license available in license.txt file, in main folder
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "StdInc.h"
|
|
|
|
#include "InputSourceTouch.h"
|
|
|
|
|
|
|
|
#include "InputHandler.h"
|
|
|
|
|
|
|
|
#include "../../lib/CConfigHandler.h"
|
2023-06-05 15:14:01 +02:00
|
|
|
#include "../CGameInfo.h"
|
|
|
|
#include "../gui/CursorHandler.h"
|
2023-05-18 19:32:29 +02:00
|
|
|
#include "../gui/CGuiHandler.h"
|
|
|
|
#include "../gui/EventDispatcher.h"
|
2023-05-18 22:31:05 +02:00
|
|
|
#include "../gui/MouseButton.h"
|
2023-06-11 19:38:42 +02:00
|
|
|
#include "../gui/WindowHandler.h"
|
2024-03-26 13:15:45 +02:00
|
|
|
#include "../CServerHandler.h"
|
|
|
|
#include "../globalLobby/GlobalLobbyClient.h"
|
2023-05-18 19:32:29 +02:00
|
|
|
|
2023-07-08 21:02:03 +02:00
|
|
|
#if defined(VCMI_ANDROID)
|
2023-07-08 17:02:44 +02:00
|
|
|
#include "../../lib/CAndroidVMHelper.h"
|
2023-07-08 21:02:03 +02:00
|
|
|
#elif defined(VCMI_IOS)
|
2023-07-08 18:15:36 +02:00
|
|
|
#include "../ios/utils.h"
|
|
|
|
#endif
|
2023-07-08 17:02:44 +02:00
|
|
|
|
2023-05-18 19:32:29 +02:00
|
|
|
#include <SDL_events.h>
|
2023-05-19 17:56:20 +02:00
|
|
|
#include <SDL_hints.h>
|
2023-05-26 17:55:26 +02:00
|
|
|
#include <SDL_timer.h>
|
2023-05-18 19:32:29 +02:00
|
|
|
|
|
|
|
InputSourceTouch::InputSourceTouch()
|
2023-09-18 16:05:39 +02:00
|
|
|
: lastTapTimeTicks(0), lastLeftClickTimeTicks(0)
|
2023-05-18 19:32:29 +02:00
|
|
|
{
|
2023-05-26 17:55:26 +02:00
|
|
|
params.useRelativeMode = settings["general"]["userRelativePointer"].Bool();
|
|
|
|
params.relativeModeSpeedFactor = settings["general"]["relativePointerSpeedMultiplier"].Float();
|
2023-06-16 11:59:20 +02:00
|
|
|
params.longTouchTimeMilliseconds = settings["general"]["longTouchTimeMilliseconds"].Float();
|
2023-07-08 19:11:26 +02:00
|
|
|
params.hapticFeedbackEnabled = settings["general"]["hapticFeedback"].Bool();
|
2023-09-08 15:54:51 +02:00
|
|
|
params.touchToleranceDistance = settings["input"]["touchToleranceDistance"].Float();
|
2023-05-26 17:55:26 +02:00
|
|
|
|
|
|
|
if (params.useRelativeMode)
|
|
|
|
state = TouchState::RELATIVE_MODE;
|
|
|
|
else
|
|
|
|
state = TouchState::IDLE;
|
|
|
|
|
2023-06-16 12:07:02 +02:00
|
|
|
#ifdef VCMI_EMULATE_TOUCHSCREEN_WITH_MOUSE
|
|
|
|
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "1");
|
|
|
|
#else
|
2023-05-26 17:55:26 +02:00
|
|
|
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
|
2023-06-16 12:07:02 +02:00
|
|
|
#endif
|
2023-05-26 17:55:26 +02:00
|
|
|
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
|
2023-05-18 19:32:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void InputSourceTouch::handleEventFingerMotion(const SDL_TouchFingerEvent & tfinger)
|
|
|
|
{
|
2024-06-26 18:39:35 +02:00
|
|
|
if (CCS && CCS->curh && settings["video"]["cursor"].String() == "software" && state != TouchState::RELATIVE_MODE)
|
|
|
|
CCS->curh->cursorMove(GH.getCursorPosition().x, GH.getCursorPosition().y);
|
|
|
|
|
2023-05-26 17:55:26 +02:00
|
|
|
switch(state)
|
2023-05-18 19:32:29 +02:00
|
|
|
{
|
2023-05-26 17:55:26 +02:00
|
|
|
case TouchState::RELATIVE_MODE:
|
|
|
|
{
|
|
|
|
Point screenSize = GH.screenDimensions();
|
2023-05-26 14:55:31 +02:00
|
|
|
|
2023-05-26 17:55:26 +02:00
|
|
|
Point moveDistance {
|
|
|
|
static_cast<int>(screenSize.x * params.relativeModeSpeedFactor * tfinger.dx),
|
|
|
|
static_cast<int>(screenSize.y * params.relativeModeSpeedFactor * tfinger.dy)
|
|
|
|
};
|
2023-05-26 14:55:31 +02:00
|
|
|
|
2023-05-26 17:55:26 +02:00
|
|
|
GH.input().moveCursorPosition(moveDistance);
|
2023-06-05 15:14:01 +02:00
|
|
|
if (CCS && CCS->curh)
|
|
|
|
CCS->curh->cursorMove(GH.getCursorPosition().x, GH.getCursorPosition().y);
|
|
|
|
|
2023-05-26 17:55:26 +02:00
|
|
|
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:
|
2024-11-02 17:09:29 +02:00
|
|
|
case TouchState::TAP_DOWN_LONG_AWAIT:
|
2023-05-26 17:55:26 +02:00
|
|
|
{
|
2023-05-30 23:33:10 +02:00
|
|
|
Point distance = convertTouchToMouse(tfinger) - lastTapPosition;
|
2023-05-26 21:17:36 +02:00
|
|
|
if ( std::abs(distance.x) > params.panningSensitivityThreshold || std::abs(distance.y) > params.panningSensitivityThreshold)
|
2023-07-22 11:33:05 +02:00
|
|
|
{
|
2024-11-02 17:09:29 +02:00
|
|
|
state = state == TouchState::TAP_DOWN_SHORT ? TouchState::TAP_DOWN_PANNING : TouchState::TAP_DOWN_PANNING_POPUP;
|
2023-07-22 11:33:05 +02:00
|
|
|
GH.events().dispatchGesturePanningStarted(lastTapPosition);
|
|
|
|
}
|
2023-05-26 17:55:26 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case TouchState::TAP_DOWN_PANNING:
|
2024-11-02 17:09:29 +02:00
|
|
|
case TouchState::TAP_DOWN_PANNING_POPUP:
|
2023-05-26 17:55:26 +02:00
|
|
|
{
|
2023-05-26 21:17:36 +02:00
|
|
|
emitPanningEvent(tfinger);
|
2023-05-26 17:55:26 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case TouchState::TAP_DOWN_DOUBLE:
|
|
|
|
{
|
2023-05-26 21:17:36 +02:00
|
|
|
emitPinchEvent(tfinger);
|
2023-05-26 17:55:26 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case TouchState::TAP_DOWN_LONG:
|
|
|
|
{
|
|
|
|
// no-op
|
|
|
|
break;
|
|
|
|
}
|
2023-05-18 19:32:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void InputSourceTouch::handleEventFingerDown(const SDL_TouchFingerEvent & tfinger)
|
|
|
|
{
|
2023-06-16 11:59:20 +02:00
|
|
|
// FIXME: better place to update potentially changed settings?
|
|
|
|
params.longTouchTimeMilliseconds = settings["general"]["longTouchTimeMilliseconds"].Float();
|
2023-07-08 20:34:11 +02:00
|
|
|
params.hapticFeedbackEnabled = settings["general"]["hapticFeedback"].Bool();
|
2023-06-16 11:59:20 +02:00
|
|
|
|
2023-05-26 17:55:26 +02:00
|
|
|
lastTapTimeTicks = tfinger.timestamp;
|
|
|
|
|
|
|
|
switch(state)
|
2023-05-18 19:32:29 +02:00
|
|
|
{
|
2023-05-26 17:55:26 +02:00
|
|
|
case TouchState::RELATIVE_MODE:
|
2023-05-18 19:32:29 +02:00
|
|
|
{
|
2023-05-26 17:55:26 +02:00
|
|
|
if(tfinger.x > 0.5)
|
|
|
|
{
|
2023-06-11 19:38:42 +02:00
|
|
|
if (tfinger.y < 0.5)
|
2023-09-06 00:06:01 +02:00
|
|
|
GH.events().dispatchShowPopup(GH.getCursorPosition(), params.touchToleranceDistance);
|
2023-06-11 19:38:42 +02:00
|
|
|
else
|
2023-09-06 00:06:01 +02:00
|
|
|
GH.events().dispatchMouseLeftButtonPressed(GH.getCursorPosition(), params.touchToleranceDistance);
|
2023-05-26 17:55:26 +02:00
|
|
|
}
|
|
|
|
break;
|
2023-05-18 19:32:29 +02:00
|
|
|
}
|
2023-05-26 17:55:26 +02:00
|
|
|
case TouchState::IDLE:
|
2023-05-26 14:55:31 +02:00
|
|
|
{
|
2023-05-30 23:33:10 +02:00
|
|
|
lastTapPosition = convertTouchToMouse(tfinger);
|
|
|
|
GH.input().setCursorPosition(lastTapPosition);
|
2023-05-26 17:55:26 +02:00
|
|
|
state = TouchState::TAP_DOWN_SHORT;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case TouchState::TAP_DOWN_SHORT:
|
2023-07-23 10:46:11 +02:00
|
|
|
{
|
|
|
|
GH.input().setCursorPosition(convertTouchToMouse(tfinger));
|
|
|
|
GH.events().dispatchGesturePanningStarted(lastTapPosition);
|
|
|
|
state = TouchState::TAP_DOWN_DOUBLE;
|
|
|
|
break;
|
|
|
|
}
|
2023-05-26 17:55:26 +02:00
|
|
|
case TouchState::TAP_DOWN_PANNING:
|
|
|
|
{
|
|
|
|
GH.input().setCursorPosition(convertTouchToMouse(tfinger));
|
|
|
|
state = TouchState::TAP_DOWN_DOUBLE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case TouchState::TAP_DOWN_DOUBLE:
|
2024-03-26 13:15:45 +02:00
|
|
|
{
|
|
|
|
CSH->getGlobalLobby().activateInterface();
|
|
|
|
break;
|
|
|
|
}
|
2023-05-29 18:21:09 +02:00
|
|
|
case TouchState::TAP_DOWN_LONG_AWAIT:
|
2024-11-02 17:09:29 +02:00
|
|
|
lastTapPosition = convertTouchToMouse(tfinger);
|
|
|
|
break;
|
|
|
|
case TouchState::TAP_DOWN_LONG:
|
|
|
|
case TouchState::TAP_DOWN_PANNING_POPUP:
|
2023-05-26 17:55:26 +02:00
|
|
|
{
|
|
|
|
// no-op
|
|
|
|
break;
|
2023-05-26 14:55:31 +02:00
|
|
|
}
|
2023-05-18 19:32:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void InputSourceTouch::handleEventFingerUp(const SDL_TouchFingerEvent & tfinger)
|
|
|
|
{
|
2023-05-26 17:55:26 +02:00
|
|
|
switch(state)
|
2023-05-18 19:32:29 +02:00
|
|
|
{
|
2023-05-26 17:55:26 +02:00
|
|
|
case TouchState::RELATIVE_MODE:
|
2023-05-18 19:32:29 +02:00
|
|
|
{
|
2023-05-26 17:55:26 +02:00
|
|
|
if(tfinger.x > 0.5)
|
|
|
|
{
|
2023-06-11 19:38:42 +02:00
|
|
|
if (tfinger.y < 0.5)
|
|
|
|
GH.events().dispatchClosePopup(GH.getCursorPosition());
|
|
|
|
else
|
2023-09-06 00:06:01 +02:00
|
|
|
GH.events().dispatchMouseLeftButtonReleased(GH.getCursorPosition(), params.touchToleranceDistance);
|
2023-05-26 17:55:26 +02:00
|
|
|
}
|
|
|
|
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));
|
2023-09-18 16:43:16 +02:00
|
|
|
if(tfinger.timestamp - lastLeftClickTimeTicks < params.doubleTouchTimeMilliseconds && (convertTouchToMouse(tfinger) - lastLeftClickPosition).length() < params.doubleTouchToleranceDistance)
|
2023-09-18 20:35:23 +02:00
|
|
|
{
|
|
|
|
GH.events().dispatchMouseDoubleClick(convertTouchToMouse(tfinger), params.touchToleranceDistance);
|
|
|
|
GH.events().dispatchMouseLeftButtonReleased(convertTouchToMouse(tfinger), params.touchToleranceDistance);
|
|
|
|
}
|
2023-09-18 16:05:39 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
GH.events().dispatchMouseLeftButtonPressed(convertTouchToMouse(tfinger), params.touchToleranceDistance);
|
|
|
|
GH.events().dispatchMouseLeftButtonReleased(convertTouchToMouse(tfinger), params.touchToleranceDistance);
|
2023-09-18 16:43:16 +02:00
|
|
|
lastLeftClickTimeTicks = tfinger.timestamp;
|
|
|
|
lastLeftClickPosition = convertTouchToMouse(tfinger);
|
2023-09-18 16:05:39 +02:00
|
|
|
}
|
2023-05-26 17:55:26 +02:00
|
|
|
state = TouchState::IDLE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case TouchState::TAP_DOWN_PANNING:
|
2024-11-02 17:09:29 +02:00
|
|
|
case TouchState::TAP_DOWN_PANNING_POPUP:
|
2023-05-26 17:55:26 +02:00
|
|
|
{
|
2023-05-30 23:33:10 +02:00
|
|
|
GH.events().dispatchGesturePanningEnded(lastTapPosition, convertTouchToMouse(tfinger));
|
2024-11-02 17:09:29 +02:00
|
|
|
state = state == TouchState::TAP_DOWN_PANNING ? TouchState::IDLE : TouchState::TAP_DOWN_LONG_AWAIT;
|
2023-05-26 17:55:26 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case TouchState::TAP_DOWN_DOUBLE:
|
|
|
|
{
|
|
|
|
if (SDL_GetNumTouchFingers(tfinger.touchId) == 1)
|
|
|
|
state = TouchState::TAP_DOWN_PANNING;
|
2023-05-26 20:46:09 +02:00
|
|
|
if (SDL_GetNumTouchFingers(tfinger.touchId) == 0)
|
2023-05-29 12:08:08 +02:00
|
|
|
{
|
2023-05-30 23:33:10 +02:00
|
|
|
GH.events().dispatchGesturePanningEnded(lastTapPosition, convertTouchToMouse(tfinger));
|
2023-05-26 20:46:09 +02:00
|
|
|
state = TouchState::IDLE;
|
2023-05-29 12:08:08 +02:00
|
|
|
}
|
2023-05-26 17:55:26 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case TouchState::TAP_DOWN_LONG:
|
2023-05-29 18:21:09 +02:00
|
|
|
{
|
|
|
|
if (SDL_GetNumTouchFingers(tfinger.touchId) == 0)
|
|
|
|
{
|
|
|
|
state = TouchState::TAP_DOWN_LONG_AWAIT;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case TouchState::TAP_DOWN_LONG_AWAIT:
|
2023-05-26 17:55:26 +02:00
|
|
|
{
|
|
|
|
if (SDL_GetNumTouchFingers(tfinger.touchId) == 0)
|
|
|
|
{
|
|
|
|
GH.input().setCursorPosition(convertTouchToMouse(tfinger));
|
2023-06-11 19:38:42 +02:00
|
|
|
GH.events().dispatchClosePopup(convertTouchToMouse(tfinger));
|
2023-05-26 17:55:26 +02:00
|
|
|
state = TouchState::IDLE;
|
|
|
|
}
|
|
|
|
break;
|
2023-05-18 19:32:29 +02:00
|
|
|
}
|
|
|
|
}
|
2023-05-26 17:55:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void InputSourceTouch::handleUpdate()
|
|
|
|
{
|
|
|
|
if ( state == TouchState::TAP_DOWN_SHORT)
|
2023-05-18 19:32:29 +02:00
|
|
|
{
|
2023-05-26 17:55:26 +02:00
|
|
|
uint32_t currentTime = SDL_GetTicks();
|
2023-06-16 11:59:20 +02:00
|
|
|
if (currentTime > lastTapTimeTicks + params.longTouchTimeMilliseconds)
|
2023-05-26 14:55:31 +02:00
|
|
|
{
|
2023-09-06 00:06:01 +02:00
|
|
|
GH.events().dispatchShowPopup(GH.getCursorPosition(), params.touchToleranceDistance);
|
2023-06-11 19:38:42 +02:00
|
|
|
|
|
|
|
if (GH.windows().isTopWindowPopup())
|
2023-07-14 22:49:44 +02:00
|
|
|
{
|
2023-07-14 22:47:13 +02:00
|
|
|
hapticFeedback();
|
2023-06-11 19:38:42 +02:00
|
|
|
state = TouchState::TAP_DOWN_LONG;
|
2023-07-14 22:49:44 +02:00
|
|
|
}
|
2023-05-26 14:55:31 +02:00
|
|
|
}
|
2023-05-18 19:32:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Point InputSourceTouch::convertTouchToMouse(const SDL_TouchFingerEvent & tfinger)
|
|
|
|
{
|
2023-05-31 15:15:15 +02:00
|
|
|
return convertTouchToMouse(tfinger.x, tfinger.y);
|
|
|
|
}
|
|
|
|
|
|
|
|
Point InputSourceTouch::convertTouchToMouse(float x, float y)
|
|
|
|
{
|
|
|
|
return Point(x * GH.screenDimensions().x, y * GH.screenDimensions().y);
|
2023-05-18 19:32:29 +02:00
|
|
|
}
|
|
|
|
|
2023-05-30 19:08:27 +02:00
|
|
|
bool InputSourceTouch::hasTouchInputDevice() const
|
|
|
|
{
|
|
|
|
return SDL_GetNumTouchDevices() > 0;
|
|
|
|
}
|
|
|
|
|
2023-05-26 21:17:36 +02:00
|
|
|
void InputSourceTouch::emitPanningEvent(const SDL_TouchFingerEvent & tfinger)
|
2023-05-26 17:55:26 +02:00
|
|
|
{
|
2023-05-31 15:15:15 +02:00
|
|
|
Point distance = convertTouchToMouse(-tfinger.dx, -tfinger.dy);
|
2023-05-26 21:17:36 +02:00
|
|
|
|
2023-05-30 23:33:10 +02:00
|
|
|
GH.events().dispatchGesturePanning(lastTapPosition, convertTouchToMouse(tfinger), distance);
|
2023-05-26 17:55:26 +02:00
|
|
|
}
|
|
|
|
|
2023-05-26 21:17:36 +02:00
|
|
|
void InputSourceTouch::emitPinchEvent(const SDL_TouchFingerEvent & tfinger)
|
2023-05-26 17:55:26 +02:00
|
|
|
{
|
2023-05-31 15:15:15 +02:00
|
|
|
int fingers = SDL_GetNumTouchFingers(tfinger.touchId);
|
|
|
|
|
|
|
|
if (fingers < 2)
|
|
|
|
return;
|
|
|
|
|
|
|
|
bool otherFingerFound = false;
|
|
|
|
double otherX;
|
|
|
|
double otherY;
|
|
|
|
|
|
|
|
for (int i = 0; i < fingers; ++i)
|
|
|
|
{
|
|
|
|
SDL_Finger * finger = SDL_GetTouchFinger(tfinger.touchId, i);
|
|
|
|
|
|
|
|
if (finger && finger->id != tfinger.fingerId)
|
|
|
|
{
|
|
|
|
otherX = finger->x * GH.screenDimensions().x;
|
|
|
|
otherY = finger->y * GH.screenDimensions().y;
|
|
|
|
otherFingerFound = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!otherFingerFound)
|
|
|
|
return; // should be impossible, but better to avoid weird edge cases
|
|
|
|
|
|
|
|
float thisX = tfinger.x * GH.screenDimensions().x;
|
|
|
|
float thisY = tfinger.y * GH.screenDimensions().y;
|
|
|
|
float deltaX = tfinger.dx * GH.screenDimensions().x;
|
|
|
|
float deltaY = tfinger.dy * GH.screenDimensions().y;
|
|
|
|
|
|
|
|
float oldX = thisX - deltaX - otherX;
|
|
|
|
float oldY = thisY - deltaY - otherY;
|
|
|
|
float newX = thisX - otherX;
|
|
|
|
float newY = thisY - otherY;
|
|
|
|
|
|
|
|
double distanceOld = std::sqrt(oldX * oldX + oldY + oldY);
|
|
|
|
double distanceNew = std::sqrt(newX * newX + newY + newY);
|
|
|
|
|
|
|
|
if (distanceOld > params.pinchSensitivityThreshold)
|
|
|
|
GH.events().dispatchGesturePinch(lastTapPosition, distanceNew / distanceOld);
|
2023-05-26 17:55:26 +02:00
|
|
|
}
|
2023-07-08 17:02:44 +02:00
|
|
|
|
|
|
|
void InputSourceTouch::hapticFeedback() {
|
2023-07-08 21:02:03 +02:00
|
|
|
if(params.hapticFeedbackEnabled) {
|
2023-07-08 17:02:44 +02:00
|
|
|
#if defined(VCMI_ANDROID)
|
2023-07-08 21:02:03 +02:00
|
|
|
CAndroidVMHelper vmHelper;
|
|
|
|
vmHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "hapticFeedback");
|
|
|
|
#elif defined(VCMI_IOS)
|
2023-07-08 18:47:38 +02:00
|
|
|
iOS_utils::hapticFeedback();
|
2023-07-08 18:15:36 +02:00
|
|
|
#endif
|
2023-07-08 18:47:38 +02:00
|
|
|
}
|
2023-07-08 17:02:44 +02:00
|
|
|
}
|