From c82806f9cb88f390ae3fb048ba7ff2bb138fa3ec Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Sun, 22 May 2022 20:34:32 -0500
Subject: [PATCH] input_common: touch: Rewrite touch driver to support multiple
 touch points

---
 src/core/hid/emulated_console.cpp             | 15 +++-
 src/core/hid/input_converter.cpp              |  4 +
 .../service/hid/controllers/touchscreen.cpp   |  6 +-
 src/input_common/drivers/touch_screen.cpp     | 89 +++++++++++++++----
 src/input_common/drivers/touch_screen.h       | 52 ++++++++---
 src/yuzu/bootmanager.cpp                      | 58 ++----------
 src/yuzu/bootmanager.h                        |  6 --
 src/yuzu_cmd/emu_window/emu_window_sdl2.cpp   |  2 +-
 8 files changed, 140 insertions(+), 92 deletions(-)

diff --git a/src/core/hid/emulated_console.cpp b/src/core/hid/emulated_console.cpp
index fd220ccb56..aac45907d4 100644
--- a/src/core/hid/emulated_console.cpp
+++ b/src/core/hid/emulated_console.cpp
@@ -27,12 +27,19 @@ void EmulatedConsole::SetTouchParams() {
         // We can't use mouse as touch if native mouse is enabled
         touch_params[index++] = Common::ParamPackage{"engine:mouse,axis_x:10,axis_y:11,button:0"};
     }
-    touch_params[index++] = Common::ParamPackage{"engine:touch,axis_x:0,axis_y:1,button:0"};
-    touch_params[index++] = Common::ParamPackage{"engine:touch,axis_x:2,axis_y:3,button:1"};
+
     touch_params[index++] =
-        Common::ParamPackage{"engine:cemuhookudp,axis_x:17,axis_y:18,button:65536"};
+        Common::ParamPackage{"engine:touch,axis_x:0,axis_y:1,button:0,touch_id:0"};
     touch_params[index++] =
-        Common::ParamPackage{"engine:cemuhookudp,axis_x:19,axis_y:20,button:131072"};
+        Common::ParamPackage{"engine:touch,axis_x:2,axis_y:3,button:1,touch_id:1"};
+    touch_params[index++] =
+        Common::ParamPackage{"engine:touch,axis_x:4,axis_y:5,button:2,touch_id:2"};
+    touch_params[index++] =
+        Common::ParamPackage{"engine:touch,axis_x:6,axis_y:7,button:3,touch_id:3"};
+    touch_params[index++] =
+        Common::ParamPackage{"engine:cemuhookudp,axis_x:17,axis_y:18,button:65536,touch_id:0"};
+    touch_params[index++] =
+        Common::ParamPackage{"engine:cemuhookudp,axis_x:19,axis_y:20,button:131072,touch_id:1"};
 
     const auto button_index =
         static_cast<u64>(Settings::values.touch_from_button_map_index.GetValue());
diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp
index 3c26260f37..18d9f042da 100644
--- a/src/core/hid/input_converter.cpp
+++ b/src/core/hid/input_converter.cpp
@@ -1,6 +1,7 @@
 // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
 // SPDX-License-Identifier: GPL-2.0-or-later
 
+#include <algorithm>
 #include <random>
 
 #include "common/input.h"
@@ -196,6 +197,9 @@ Common::Input::TouchStatus TransformToTouch(const Common::Input::CallbackStatus&
     x = std::clamp(x, 0.0f, 1.0f);
     y = std::clamp(y, 0.0f, 1.0f);
 
+    // Limit id to maximum number of fingers
+    status.id = std::clamp(status.id, 0, 16);
+
     if (status.pressed.inverted) {
         status.pressed.value = !status.pressed.value;
     }
diff --git a/src/core/hle/service/hid/controllers/touchscreen.cpp b/src/core/hle/service/hid/controllers/touchscreen.cpp
index 108ce5a415..1da8d3eb0b 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.cpp
+++ b/src/core/hle/service/hid/controllers/touchscreen.cpp
@@ -44,7 +44,6 @@ void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timin
     for (std::size_t id = 0; id < MAX_FINGERS; id++) {
         const auto& current_touch = touch_status[id];
         auto& finger = fingers[id];
-        finger.position = current_touch.position;
         finger.id = current_touch.id;
 
         if (finger.attribute.start_touch) {
@@ -61,13 +60,18 @@ void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timin
         if (!finger.pressed && current_touch.pressed) {
             finger.attribute.start_touch.Assign(1);
             finger.pressed = true;
+            finger.position = current_touch.position;
             continue;
         }
 
         if (finger.pressed && !current_touch.pressed) {
             finger.attribute.raw = 0;
             finger.attribute.end_touch.Assign(1);
+            continue;
         }
+
+        // Only update position if touch is not on a special frame
+        finger.position = current_touch.position;
     }
 
     std::array<Core::HID::TouchFinger, MAX_FINGERS> active_fingers;
diff --git a/src/input_common/drivers/touch_screen.cpp b/src/input_common/drivers/touch_screen.cpp
index 8acbe45844..1753e08933 100644
--- a/src/input_common/drivers/touch_screen.cpp
+++ b/src/input_common/drivers/touch_screen.cpp
@@ -14,38 +14,93 @@ constexpr PadIdentifier identifier = {
 
 TouchScreen::TouchScreen(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
     PreSetController(identifier);
+    ReleaseAllTouch();
 }
 
-void TouchScreen::TouchMoved(float x, float y, std::size_t finger) {
-    if (finger >= 16) {
+void TouchScreen::TouchMoved(float x, float y, std::size_t finger_id) {
+    const auto index = GetIndexFromFingerId(finger_id);
+    if (!index) {
+        // Touch doesn't exist handle it as a new one
+        TouchPressed(x, y, finger_id);
         return;
     }
-    TouchPressed(x, y, finger);
+    const auto i = index.value();
+    fingers[i].is_active = true;
+    SetButton(identifier, static_cast<int>(i), true);
+    SetAxis(identifier, static_cast<int>(i * 2), x);
+    SetAxis(identifier, static_cast<int>(i * 2 + 1), y);
 }
 
-void TouchScreen::TouchPressed(float x, float y, std::size_t finger) {
-    if (finger >= 16) {
+void TouchScreen::TouchPressed(float x, float y, std::size_t finger_id) {
+    if (GetIndexFromFingerId(finger_id)) {
+        // Touch already exist. Just update the data
+        TouchMoved(x, y, finger_id);
         return;
     }
-    SetButton(identifier, static_cast<int>(finger), true);
-    SetAxis(identifier, static_cast<int>(finger * 2), x);
-    SetAxis(identifier, static_cast<int>(finger * 2 + 1), y);
+    const auto index = GetNextFreeIndex();
+    if (!index) {
+        // No free entries. Ignore input
+        return;
+    }
+    const auto i = index.value();
+    fingers[i].is_enabled = true;
+    fingers[i].finger_id = finger_id;
+    TouchMoved(x, y, finger_id);
 }
 
-void TouchScreen::TouchReleased(std::size_t finger) {
-    if (finger >= 16) {
+void TouchScreen::TouchReleased(std::size_t finger_id) {
+    const auto index = GetIndexFromFingerId(finger_id);
+    if (!index) {
         return;
     }
-    SetButton(identifier, static_cast<int>(finger), false);
-    SetAxis(identifier, static_cast<int>(finger * 2), 0.0f);
-    SetAxis(identifier, static_cast<int>(finger * 2 + 1), 0.0f);
+    const auto i = index.value();
+    fingers[i].is_enabled = false;
+    SetButton(identifier, static_cast<int>(i), false);
+    SetAxis(identifier, static_cast<int>(i * 2), 0.0f);
+    SetAxis(identifier, static_cast<int>(i * 2 + 1), 0.0f);
+}
+
+std::optional<std::size_t> TouchScreen::GetIndexFromFingerId(std::size_t finger_id) const {
+    for (std::size_t index = 0; index < MAX_FINGER_COUNT; ++index) {
+        const auto& finger = fingers[index];
+        if (!finger.is_enabled) {
+            continue;
+        }
+        if (finger.finger_id == finger_id) {
+            return index;
+        }
+    }
+    return std::nullopt;
+}
+
+std::optional<std::size_t> TouchScreen::GetNextFreeIndex() const {
+    for (std::size_t index = 0; index < MAX_FINGER_COUNT; ++index) {
+        if (!fingers[index].is_enabled) {
+            return index;
+        }
+    }
+    return std::nullopt;
+}
+
+void TouchScreen::ClearActiveFlag() {
+    for (auto& finger : fingers) {
+        finger.is_active = false;
+    }
+}
+
+void TouchScreen::ReleaseInactiveTouch() {
+    for (const auto& finger : fingers) {
+        if (!finger.is_active) {
+            TouchReleased(finger.finger_id);
+        }
+    }
 }
 
 void TouchScreen::ReleaseAllTouch() {
-    for (int index = 0; index < 16; ++index) {
-        SetButton(identifier, index, false);
-        SetAxis(identifier, index * 2, 0.0f);
-        SetAxis(identifier, index * 2 + 1, 0.0f);
+    for (const auto& finger : fingers) {
+        if (finger.is_enabled) {
+            TouchReleased(finger.finger_id);
+        }
     }
 }
 
diff --git a/src/input_common/drivers/touch_screen.h b/src/input_common/drivers/touch_screen.h
index 193478ead8..f46036ffd8 100644
--- a/src/input_common/drivers/touch_screen.h
+++ b/src/input_common/drivers/touch_screen.h
@@ -3,41 +3,65 @@
 
 #pragma once
 
+#include <optional>
+
 #include "input_common/input_engine.h"
 
 namespace InputCommon {
 
 /**
- * A button device factory representing a keyboard. It receives keyboard events and forward them
- * to all button devices it created.
+ * A touch device factory representing a touch screen. It receives touch events and forward them
+ * to all touch devices it created.
  */
 class TouchScreen final : public InputEngine {
 public:
     explicit TouchScreen(std::string input_engine_);
 
     /**
-     * Signals that mouse has moved.
-     * @param x the x-coordinate of the cursor
-     * @param y the y-coordinate of the cursor
-     * @param center_x the x-coordinate of the middle of the screen
-     * @param center_y the y-coordinate of the middle of the screen
+     * Signals that touch has moved and marks this touch point as active
+     * @param x new horizontal position
+     * @param y new vertical position
+     * @param finger_id of the touch point to be updated
      */
-    void TouchMoved(float x, float y, std::size_t finger);
+    void TouchMoved(float x, float y, std::size_t finger_id);
 
     /**
-     * Sets the status of all buttons bound with the key to pressed
-     * @param key_code the code of the key to press
+     * Signals and creates a new touch point with this finger id
+     * @param x starting horizontal position
+     * @param y starting vertical position
+     * @param finger_id to be assigned to the new touch point
      */
-    void TouchPressed(float x, float y, std::size_t finger);
+    void TouchPressed(float x, float y, std::size_t finger_id);
 
     /**
-     * Sets the status of all buttons bound with the key to released
-     * @param key_code the code of the key to release
+     * Signals and resets the touch point related to the this finger id
+     * @param finger_id to be released
      */
-    void TouchReleased(std::size_t finger);
+    void TouchReleased(std::size_t finger_id);
+
+    /// Resets the active flag for each touch point
+    void ClearActiveFlag();
+
+    /// Releases all touch that haven't been marked as active
+    void ReleaseInactiveTouch();
 
     /// Resets all inputs to their initial value
     void ReleaseAllTouch();
+
+private:
+    static constexpr std::size_t MAX_FINGER_COUNT = 16;
+
+    struct TouchStatus {
+        std::size_t finger_id{};
+        bool is_enabled{};
+        bool is_active{};
+    };
+
+    std::optional<std::size_t> GetIndexFromFingerId(std::size_t finger_id) const;
+
+    std::optional<std::size_t> GetNextFreeIndex() const;
+
+    std::array<TouchStatus, MAX_FINGER_COUNT> fingers{};
 };
 
 } // namespace InputCommon
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index a1b819ae0c..8f0a6bbb8b 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -772,65 +772,25 @@ void GRenderWindow::wheelEvent(QWheelEvent* event) {
 void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) {
     QList<QTouchEvent::TouchPoint> touch_points = event->touchPoints();
     for (const auto& touch_point : touch_points) {
-        if (!TouchUpdate(touch_point)) {
-            TouchStart(touch_point);
-        }
+        const auto [x, y] = ScaleTouch(touch_point.pos());
+        const auto [touch_x, touch_y] = MapToTouchScreen(x, y);
+        input_subsystem->GetTouchScreen()->TouchPressed(touch_x, touch_y, touch_point.id());
     }
 }
 
 void GRenderWindow::TouchUpdateEvent(const QTouchEvent* event) {
     QList<QTouchEvent::TouchPoint> touch_points = event->touchPoints();
+    input_subsystem->GetTouchScreen()->ClearActiveFlag();
     for (const auto& touch_point : touch_points) {
-        if (!TouchUpdate(touch_point)) {
-            TouchStart(touch_point);
-        }
-    }
-    // Release all inactive points
-    for (std::size_t id = 0; id < touch_ids.size(); ++id) {
-        if (!TouchExist(touch_ids[id], touch_points)) {
-            touch_ids[id] = 0;
-            input_subsystem->GetTouchScreen()->TouchReleased(id);
-        }
+        const auto [x, y] = ScaleTouch(touch_point.pos());
+        const auto [touch_x, touch_y] = MapToTouchScreen(x, y);
+        input_subsystem->GetTouchScreen()->TouchMoved(touch_x, touch_y, touch_point.id());
     }
+    input_subsystem->GetTouchScreen()->ReleaseInactiveTouch();
 }
 
 void GRenderWindow::TouchEndEvent() {
-    for (std::size_t id = 0; id < touch_ids.size(); ++id) {
-        if (touch_ids[id] != 0) {
-            touch_ids[id] = 0;
-            input_subsystem->GetTouchScreen()->TouchReleased(id);
-        }
-    }
-}
-
-void GRenderWindow::TouchStart(const QTouchEvent::TouchPoint& touch_point) {
-    for (std::size_t id = 0; id < touch_ids.size(); ++id) {
-        if (touch_ids[id] == 0) {
-            touch_ids[id] = touch_point.id() + 1;
-            const auto [x, y] = ScaleTouch(touch_point.pos());
-            const auto [touch_x, touch_y] = MapToTouchScreen(x, y);
-            input_subsystem->GetTouchScreen()->TouchPressed(touch_x, touch_y, id);
-        }
-    }
-}
-
-bool GRenderWindow::TouchUpdate(const QTouchEvent::TouchPoint& touch_point) {
-    for (std::size_t id = 0; id < touch_ids.size(); ++id) {
-        if (touch_ids[id] == static_cast<std::size_t>(touch_point.id() + 1)) {
-            const auto [x, y] = ScaleTouch(touch_point.pos());
-            const auto [touch_x, touch_y] = MapToTouchScreen(x, y);
-            input_subsystem->GetTouchScreen()->TouchMoved(touch_x, touch_y, id);
-            return true;
-        }
-    }
-    return false;
-}
-
-bool GRenderWindow::TouchExist(std::size_t id,
-                               const QList<QTouchEvent::TouchPoint>& touch_points) const {
-    return std::any_of(touch_points.begin(), touch_points.end(), [id](const auto& point) {
-        return id == static_cast<std::size_t>(point.id() + 1);
-    });
+    input_subsystem->GetTouchScreen()->ReleaseAllTouch();
 }
 
 bool GRenderWindow::event(QEvent* event) {
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index 4b0ce0293d..8418165648 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -217,10 +217,6 @@ private:
     void TouchUpdateEvent(const QTouchEvent* event);
     void TouchEndEvent();
 
-    void TouchStart(const QTouchEvent::TouchPoint& touch_point);
-    bool TouchUpdate(const QTouchEvent::TouchPoint& touch_point);
-    bool TouchExist(std::size_t id, const QList<QTouchEvent::TouchPoint>& touch_points) const;
-
     void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
 
     bool InitializeOpenGL();
@@ -246,8 +242,6 @@ private:
     bool first_frame = false;
     InputCommon::TasInput::TasState last_tas_state;
 
-    std::array<std::size_t, 16> touch_ids{};
-
     Core::System& system;
 
 protected:
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
index ae2e62dc5e..71c413e647 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
@@ -93,7 +93,7 @@ void EmuWindow_SDL2::OnFingerMotion(float x, float y, std::size_t id) {
 }
 
 void EmuWindow_SDL2::OnFingerUp() {
-    input_subsystem->GetTouchScreen()->TouchReleased(0);
+    input_subsystem->GetTouchScreen()->ReleaseAllTouch();
 }
 
 void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) {