From 4ee914c7a8c405775a4e2e3d48a398bd7aa5113e Mon Sep 17 00:00:00 2001
From: NeatNit <neatnit@gmail.com>
Date: Mon, 1 Oct 2018 22:42:49 +0300
Subject: [PATCH] implemented touch in Qt and SDL

change TouchToPixelPos to return std::pair<int, int>

static_cast (SDL)

various minor style and code improvements

style - PascalCase for function names

made touch events private

const pointer arg in touch events

make TouchToPixelPos a const member function

did I do this right?

braces on barely-multiline if

remove question comment (confirmed in Discord)

fixed consts

remove unused parameter from TouchEndEvent

DRY - High-DPI scaled touch put in separate function

also fixes a bug where if you start touching (with either mouse or touchscreen) and drag the mouse to the LEFT of the emulator window, the touch point jumps to the RIGHT side of the touchscreen; draggin to above the window would make it jump to the bottom.

implicit conversion from QPoint to QPointF, apparently

I have no idea what const even means but I'll put it here anyway

remove unused or used-once variables

make touch scaling functions const, and put their implementations together

removed unused FingerID parameters

QTouchEvent forward declaration; add comment to TouchBegin that was lost in an edit

better DRY in SDL

To do -> TODO(NeatNit)

remove unused include
---
 src/citra/emu_window/emu_window_sdl2.cpp | 48 +++++++++++++++-
 src/citra/emu_window/emu_window_sdl2.h   | 12 ++++
 src/citra_qt/bootmanager.cpp             | 71 +++++++++++++++++++++---
 src/citra_qt/bootmanager.h               | 10 +++-
 4 files changed, 131 insertions(+), 10 deletions(-)

diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp
index 183dac64c..175480fda 100644
--- a/src/citra/emu_window/emu_window_sdl2.cpp
+++ b/src/citra/emu_window/emu_window_sdl2.cpp
@@ -41,6 +41,35 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
     }
 }
 
+std::pair<unsigned, unsigned> EmuWindow_SDL2::TouchToPixelPos(float touch_x, float touch_y) const {
+    int w, h;
+    SDL_GetWindowSize(render_window, &w, &h);
+
+    touch_x *= w;
+    touch_y *= h;
+
+    return {static_cast<unsigned>(std::max(std::round(touch_x), 0.0f)),
+            static_cast<unsigned>(std::max(std::round(touch_y), 0.0f))};
+}
+
+void EmuWindow_SDL2::OnFingerDown(float x, float y) {
+    // TODO(NeatNit): keep track of multitouch using the fingerID and a dictionary of some kind
+    // This isn't critical because the best we can do when we have that is to average them, like the
+    // 3DS does
+
+    const auto [px, py] = TouchToPixelPos(x, y);
+    TouchPressed(px, py);
+}
+
+void EmuWindow_SDL2::OnFingerMotion(float x, float y) {
+    const auto [px, py] = TouchToPixelPos(x, y);
+    TouchMoved(px, py);
+}
+
+void EmuWindow_SDL2::OnFingerUp() {
+    TouchReleased();
+}
+
 void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) {
     if (state == SDL_PRESSED) {
         InputCommon::GetKeyboard()->PressKey(key);
@@ -178,11 +207,26 @@ void EmuWindow_SDL2::PollEvents() {
             OnKeyEvent(static_cast<int>(event.key.keysym.scancode), event.key.state);
             break;
         case SDL_MOUSEMOTION:
-            OnMouseMotion(event.motion.x, event.motion.y);
+            // ignore if it came from touch
+            if (event.button.which != SDL_TOUCH_MOUSEID)
+                OnMouseMotion(event.motion.x, event.motion.y);
             break;
         case SDL_MOUSEBUTTONDOWN:
         case SDL_MOUSEBUTTONUP:
-            OnMouseButton(event.button.button, event.button.state, event.button.x, event.button.y);
+            // ignore if it came from touch
+            if (event.button.which != SDL_TOUCH_MOUSEID) {
+                OnMouseButton(event.button.button, event.button.state, event.button.x,
+                              event.button.y);
+            }
+            break;
+        case SDL_FINGERDOWN:
+            OnFingerDown(event.tfinger.x, event.tfinger.y);
+            break;
+        case SDL_FINGERMOTION:
+            OnFingerMotion(event.tfinger.x, event.tfinger.y);
+            break;
+        case SDL_FINGERUP:
+            OnFingerUp();
             break;
         case SDL_QUIT:
             is_open = false;
diff --git a/src/citra/emu_window/emu_window_sdl2.h b/src/citra/emu_window/emu_window_sdl2.h
index 7d5cfffb6..d486306ce 100644
--- a/src/citra/emu_window/emu_window_sdl2.h
+++ b/src/citra/emu_window/emu_window_sdl2.h
@@ -40,6 +40,18 @@ private:
     /// Called by PollEvents when a mouse button is pressed or released
     void OnMouseButton(u32 button, u8 state, s32 x, s32 y);
 
+    /// Translates pixel position (0..1) to pixel positions
+    std::pair<unsigned, unsigned> TouchToPixelPos(float touch_x, float touch_y) const;
+
+    /// Called by PollEvents when a finger starts touching the touchscreen
+    void OnFingerDown(float x, float y);
+
+    /// Called by PollEvents when a finger moves while touching the touchscreen
+    void OnFingerMotion(float x, float y);
+
+    /// Called by PollEvents when a finger stops touching the touchscreen
+    void OnFingerUp();
+
     /// Called by PollEvents when any event that may cause the window to be resized occurs
     void OnResize();
 
diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp
index aa828f7fd..9dd2b338e 100644
--- a/src/citra_qt/bootmanager.cpp
+++ b/src/citra_qt/bootmanager.cpp
@@ -112,6 +112,7 @@ GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
     std::string window_title = fmt::format("Citra {} | {}-{}", Common::g_build_name,
                                            Common::g_scm_branch, Common::g_scm_desc);
     setWindowTitle(QString::fromStdString(window_title));
+    setAttribute(Qt::WA_AcceptTouchEvents);
 
     InputCommon::Init();
 }
@@ -191,11 +192,17 @@ QByteArray GRenderWindow::saveGeometry() {
         return geometry;
 }
 
-qreal GRenderWindow::windowPixelRatio() {
+qreal GRenderWindow::windowPixelRatio() const {
     // windowHandle() might not be accessible until the window is displayed to screen.
     return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f;
 }
 
+std::pair<unsigned, unsigned> GRenderWindow::ScaleTouch(const QPointF pos) const {
+    const qreal pixel_ratio = windowPixelRatio();
+    return {static_cast<unsigned>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})),
+            static_cast<unsigned>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))};
+}
+
 void GRenderWindow::closeEvent(QCloseEvent* event) {
     emit Closed();
     QWidget::closeEvent(event);
@@ -210,31 +217,81 @@ void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
 }
 
 void GRenderWindow::mousePressEvent(QMouseEvent* event) {
+    if (event->source() == Qt::MouseEventSynthesizedBySystem)
+        return; // touch input is handled in TouchBeginEvent
+
     auto pos = event->pos();
     if (event->button() == Qt::LeftButton) {
-        qreal pixelRatio = windowPixelRatio();
-        this->TouchPressed(static_cast<unsigned>(pos.x() * pixelRatio),
-                           static_cast<unsigned>(pos.y() * pixelRatio));
+        const auto [x, y] = ScaleTouch(pos);
+        this->TouchPressed(x, y);
     } else if (event->button() == Qt::RightButton) {
         InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
     }
 }
 
 void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
+    if (event->source() == Qt::MouseEventSynthesizedBySystem)
+        return; // touch input is handled in TouchUpdateEvent
+
     auto pos = event->pos();
-    qreal pixelRatio = windowPixelRatio();
-    this->TouchMoved(std::max(static_cast<unsigned>(pos.x() * pixelRatio), 0u),
-                     std::max(static_cast<unsigned>(pos.y() * pixelRatio), 0u));
+    const auto [x, y] = ScaleTouch(pos);
+    this->TouchMoved(x, y);
     InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
 }
 
 void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
+    if (event->source() == Qt::MouseEventSynthesizedBySystem)
+        return; // touch input is handled in TouchEndEvent
+
     if (event->button() == Qt::LeftButton)
         this->TouchReleased();
     else if (event->button() == Qt::RightButton)
         InputCommon::GetMotionEmu()->EndTilt();
 }
 
+void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) {
+    // TouchBegin always has exactly one touch point, so take the .first()
+    const auto [x, y] = ScaleTouch(event->touchPoints().first().pos());
+    this->TouchPressed(x, y);
+}
+
+void GRenderWindow::TouchUpdateEvent(const QTouchEvent* event) {
+    QPointF pos;
+    int active_points = 0;
+
+    // average all active touch points
+    for (const auto tp : event->touchPoints()) {
+        if (tp.state() & (Qt::TouchPointPressed | Qt::TouchPointMoved | Qt::TouchPointStationary)) {
+            active_points++;
+            pos += tp.pos();
+        }
+    }
+
+    pos /= active_points;
+
+    const auto [x, y] = ScaleTouch(pos);
+    this->TouchMoved(x, y);
+}
+
+void GRenderWindow::TouchEndEvent() {
+    this->TouchReleased();
+}
+
+bool GRenderWindow::event(QEvent* event) {
+    if (event->type() == QEvent::TouchBegin) {
+        TouchBeginEvent(static_cast<QTouchEvent*>(event));
+        return true;
+    } else if (event->type() == QEvent::TouchUpdate) {
+        TouchUpdateEvent(static_cast<QTouchEvent*>(event));
+        return true;
+    } else if (event->type() == QEvent::TouchEnd || event->type() == QEvent::TouchCancel) {
+        TouchEndEvent();
+        return true;
+    }
+
+    return QWidget::event(event);
+}
+
 void GRenderWindow::focusOutEvent(QFocusEvent* event) {
     QWidget::focusOutEvent(event);
     InputCommon::GetKeyboard()->ReleaseAllKeys();
diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h
index 349bb17cb..56b00ea70 100644
--- a/src/citra_qt/bootmanager.h
+++ b/src/citra_qt/bootmanager.h
@@ -15,6 +15,7 @@
 
 class QKeyEvent;
 class QScreen;
+class QTouchEvent;
 
 class GGLWidgetInternal;
 class GMainWindow;
@@ -119,7 +120,7 @@ public:
     void restoreGeometry(const QByteArray& geometry); // overridden
     QByteArray saveGeometry();                        // overridden
 
-    qreal windowPixelRatio();
+    qreal windowPixelRatio() const;
 
     void closeEvent(QCloseEvent* event) override;
 
@@ -130,6 +131,8 @@ public:
     void mouseMoveEvent(QMouseEvent* event) override;
     void mouseReleaseEvent(QMouseEvent* event) override;
 
+    bool event(QEvent* event) override;
+
     void focusOutEvent(QFocusEvent* event) override;
 
     void OnClientAreaResized(unsigned width, unsigned height);
@@ -148,6 +151,11 @@ signals:
     void Closed();
 
 private:
+    std::pair<unsigned, unsigned> ScaleTouch(const QPointF pos) const;
+    void TouchBeginEvent(const QTouchEvent* event);
+    void TouchUpdateEvent(const QTouchEvent* event);
+    void TouchEndEvent();
+
     void OnMinimalClientAreaChangeRequest(
         const std::pair<unsigned, unsigned>& minimal_size) override;