From bb5dbf4c0f1e5d124485b0d2ffed62688c065121 Mon Sep 17 00:00:00 2001 From: KojoZero Date: Sun, 26 Apr 2026 14:18:14 -0700 Subject: [PATCH] setup cursor class and related variables/methods --- src/core/CMakeLists.txt | 2 + src/core/frontend/cursor.cpp | 270 +++++++++++++++++++++++++++++++ src/core/frontend/cursor.h | 50 ++++++ src/core/frontend/emu_window.cpp | 10 ++ src/core/frontend/emu_window.h | 3 + src/core/hle/service/hid/hid.cpp | 12 +- src/core/hle/service/hid/hid.h | 6 +- 7 files changed, 349 insertions(+), 4 deletions(-) create mode 100644 src/core/frontend/cursor.cpp create mode 100644 src/core/frontend/cursor.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index ec7a45bfe..499918581 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -121,6 +121,8 @@ add_library(citra_core STATIC frontend/camera/interface.h frontend/emu_window.cpp frontend/emu_window.h + frontend/cursor.cpp + frontend/cursor.h frontend/framebuffer_layout.cpp frontend/framebuffer_layout.h frontend/image_interface.cpp diff --git a/src/core/frontend/cursor.cpp b/src/core/frontend/cursor.cpp new file mode 100644 index 000000000..332f9d4c6 --- /dev/null +++ b/src/core/frontend/cursor.cpp @@ -0,0 +1,270 @@ +#include "cursor.h" +#include +#include +#include "common/logging/log.h" +#include "core\hle\service\hid\hid.h" + + +void Cursor::update(){ + if (emuWindow != nullptr){ + stylusInput = Service::HID::Module::getStylusInputs(); + modButtons = Service::HID::Module::getModButtons(); + if (deviceInUse == 0){ + if (inMacro){ + runMacro(); + } else { + // Reset the cursor position if macro was just played + if (justFinishedMacro > 0){ + justFinishedMacro = 0; + rawCursorPos[0] = macroInitPos[0]; + rawCursorPos[1] = macroInitPos[1]; + } + + // Macros + if (modButtons[0]){ + circle(0); + return; + } else if (modButtons[1]){ + rub(); + return; + } else if (modButtons[2]){ + if (!macroBtnPressed){ + // Add macro + return; + } + } else if (modButtons[3]){ + circle(1); + return; + } else { + if (macroBtnPressed){ + macroBtnPressed = false; + } + } + + normStylusDirection[0] = stylusInput[0]; + normStylusDirection[1] = stylusInput[1]; + + int maxSpeed = 50; + float multiplier = 0.5f * pow(4.0f, maxSpeed / 100.0f); // 0 is 0.5x speed, 100 is 2.0x speed. + float heightSpeed = (240.0f / 33.0f) * multiplier; + bool stylusModPressed = stylusInput[3]; + float responsecurve = 175.0f / 100.0f; + float speedupratio = 400.0f / 100.0f; + float joystickScaled[2] = {0.0f}; + float radialLength = std::sqrt((normStylusDirection[0] * normStylusDirection[0]) + (normStylusDirection[1] * normStylusDirection[1])); + float finalLength; + float curvedLength; + if (radialLength > 0) { + // Get X and Y as a relation to the radial length + float rComponents[2]; + rComponents[0] = normStylusDirection[0]/radialLength; + rComponents[1] = normStylusDirection[1]/radialLength; + // Apply response curve and output + curvedLength = std::pow(radialLength, responsecurve); + finalLength = stylusModPressed ? curvedLength * speedupratio : curvedLength; + joystickScaled[0] = rComponents[0] * finalLength; + joystickScaled[1] = rComponents[1] * finalLength; + } + // The code below sets the cursor position to the position of the joystick (absolute). Needs to be readjusted for standalone melonDS + // _joystickCursorPosition = vec2((NDS_SCREEN_WIDTH/2.0f)+(std::min(1.0,(normStylusDirection[0]/0.7071))*(NDS_SCREEN_WIDTH/2.0f)), (NDS_SCREEN_HEIGHT/2.0f)+(std::min(1.0,(normStylusDirection[1]/0.7071))*(NDS_SCREEN_HEIGHT/2.0f))); + + float tempX = joystickScaled[0]; + float tempY = joystickScaled[1]; + + switch (rotation) + { + case 1: // 90° + joystickScaled[0] = tempY; + joystickScaled[1] = -tempX; + break; + case 2: // 180° + joystickScaled[0] = -tempX; + joystickScaled[1] = -tempY; + break; + case 3: // 270° + joystickScaled[0] = -tempY; + joystickScaled[1] = tempX; + break; + default: + break; + } + + rawCursorPos[0] += joystickScaled[0]*heightSpeed; + rawCursorPos[1] += joystickScaled[1]*heightSpeed; + + // Clamp to region and ready position information for touchscreen + clamp(); + updateCursorPos(); + + // Handle stylus touch button presses + if (stylusInput[5]){ + touchScreen(); + wasTouching = true; + } else if (wasTouching && !stylusInput[5]){ + release(); + wasTouching = false; + } + } + } else { + //Update cursor based on mouse position + clamp(); + updateCursorPos(); + + // Handle stylus touch button presses + if (stylusInput[5]){ + touchScreen(); + wasTouching = true; + } else if (wasTouching && !stylusInput[5]){ + release(); + wasTouching = false; + } + + setDeviceInUse(0); + } + } +} + +void Cursor::setDeviceInUse(int device){ + deviceInUse = device; +} + +void Cursor::setRawCursorPos(float x, float y){ + rawCursorPos[0] = x; + rawCursorPos[1] = y; +} + +void Cursor::clamp(){ + rawCursorPos[0] = std::clamp(rawCursorPos[0], 0.0f, 319.0f); + rawCursorPos[1] = std::clamp(rawCursorPos[1], 0.0f, 239.0f); +} + +void Cursor::touchScreen(){ + emuWindow->TouchDirectlyPressed(cursorPos[0], cursorPos[1]); +} + +void Cursor::release(){ + emuWindow->TouchReleased(); +} +void Cursor::setEmuWindow(Frontend::EmuWindow* emuWindow){ + this->emuWindow = emuWindow; +} + +void Cursor::updateCursorPos(){ + cursorPos[0] = std::floor(rawCursorPos[0]); + cursorPos[1] = std::floor(rawCursorPos[1]); +} + +void Cursor::circle(int direction){ + macroBtnPressed = true; + inMacro = true; + wasTouching = true; + macroType = 1; + float radius = 240.0f/4.0f; + if (justFinishedMacro != 1){ // Set the original position if just starting + macroInitPos = {rawCursorPos[0], rawCursorPos[1]}; + } + + + std::vector> offsetArray; + if (direction == 0){ + offsetArray.push_back({(0.0f*radius), (-1.0f*radius)}); + offsetArray.push_back({(0.7071f*radius), (-0.7071f*radius)}); + offsetArray.push_back({(1.0f*radius), (0.0f*radius)}); + offsetArray.push_back({(0.7071f*radius), (0.7071f*radius)}); + offsetArray.push_back({(0.0f*radius), (1.0f*radius)}); + offsetArray.push_back({(-0.7071f*radius), (0.7071f*radius)}); + offsetArray.push_back({(-1.0f*radius), (0.0f*radius)}); + offsetArray.push_back({(-0.7071f*radius), (-0.7071f*radius)}); + } else { + offsetArray.push_back({(0.0f*radius), (-1.0f*radius)}); + offsetArray.push_back({(-0.7071f*radius), (-0.7071f*radius)}); + offsetArray.push_back({(-1.0f*radius), (0.0f*radius)}); + offsetArray.push_back({(-0.7071f*radius), (0.7071f*radius)}); + offsetArray.push_back({(0.0f*radius), (1.0f*radius)}); + offsetArray.push_back({(0.7071f*radius), (0.7071f*radius)}); + offsetArray.push_back({(1.0f*radius), (0.0f*radius)}); + offsetArray.push_back({(0.7071f*radius), (-0.7071f*radius)}); + } + offsetArray = rotateVector(offsetArray); + + for (int i = 0; i < offsetArray.size(); i++){ + macroPositions.push_back({rawCursorPos[0]+offsetArray[i][0], rawCursorPos[1]+offsetArray[i][1]}); + } + macroFrames = macroPositions.size(); + runMacro(); +} + +void Cursor::rub(){ + macroBtnPressed = true; + inMacro = true; + wasTouching = true; + macroType = 2; + float radius = 240.0f/6.0f; + if (justFinishedMacro != 2){ // Set the original position if just starting + macroInitPos = {rawCursorPos[0], rawCursorPos[1]}; + } + std::vector> offsetArray; + offsetArray.push_back({(0.0f*radius), 0}); + offsetArray.push_back({(0.5f*radius), 0}); + offsetArray.push_back({(1.0f*radius), 0}); + offsetArray.push_back({(0.5f*radius), 0}); + offsetArray.push_back({(0.0f*radius), 0}); + offsetArray.push_back({(-0.5f*radius), 0}); + offsetArray.push_back({(-1.0f*radius), 0}); + offsetArray.push_back({(-0.5f*radius), 0}); + offsetArray = rotateVector(offsetArray); + + for (int i = 0; i < offsetArray.size(); i++){ + macroPositions.push_back({rawCursorPos[0]+offsetArray[i][0], rawCursorPos[1]+offsetArray[i][1]}); + } + macroFrames = macroPositions.size(); + runMacro(); +} + + +void Cursor::runMacro(){ + rawCursorPos[0] = macroPositions.front()[0]; + rawCursorPos[1] = macroPositions.front()[1]; + macroPositions.pop_front(); + clamp(); + updateCursorPos(); + touchScreen(); + macroFrames--; + if (macroFrames == 0){ + macroPositions.clear(); + inMacro = false; + justFinishedMacro = macroType; + } +} + +void Cursor::setRotation(int rot){ + rotation = rot; +} +void Cursor::setLayout(int lay){ + layout = lay; +} + +std::vector> Cursor::rotateVector(std::vector> input){ + for (auto& currArray : input){ + float tempX = currArray[0]; + float tempY = currArray[1]; + switch (rotation) + { + case 1: // 90° + currArray[0] = tempY; + currArray[1] = -tempX; + break; + case 2: // 180° + currArray[0] = -tempX; + currArray[1] = -tempY; + break; + case 3: // 270° + currArray[0] = -tempY; + currArray[1] = tempX; + break; + default: + break; + } + } + return input; +} diff --git a/src/core/frontend/cursor.h b/src/core/frontend/cursor.h new file mode 100644 index 000000000..5dd50b979 --- /dev/null +++ b/src/core/frontend/cursor.h @@ -0,0 +1,50 @@ +#ifndef CURSOR_H +#define CURSOR_H +#include +#include "emu_window.h" +#include +#include +namespace Frontend { + class EmuWindow; +} + +class Cursor +{ +public: + void update(); + void setRawCursorPos(float x, float y); + void setDeviceInUse(int device); + void setEmuWindow(Frontend::EmuWindow* emuWindow); + void setRotation(int rot); + void setLayout(int layout); + int cursorPos[2]; + float normStylusDirection[2]; + bool cursorEnabled = true; +private: + Frontend::EmuWindow* emuWindow = nullptr; + float rawCursorPos[2] = {159, 119}; + void touchScreen(); + void release(); + void clamp(); + void updateCursorPos(); + bool wasTouching; + std::array stylusInput; + std::array modButtons; + void circle(int direction); //0 is clockwise, 1 is counter clockwise + void rub(); + void runMacro(); + + std::vector> rotateVector(std::vector> input); + bool inMacro; + std::deque> macroPositions; + int macroFrames; + bool macroBtnPressed; + int macroType; + int justFinishedMacro; + std::array macroInitPos; + int rotation; + int layout; + int deviceInUse; //0 is Gamepad, 1 is Mouse/Tablet +}; + +#endif // CURSOR_H \ No newline at end of file diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index 3216a1675..0ebfc46e8 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include #include #include "common/settings.h" @@ -191,6 +192,15 @@ bool EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) { return true; } +bool EmuWindow::TouchDirectlyPressed(unsigned internal_x, unsigned internal_y) { + std::scoped_lock guard{touch_state->mutex}; + std::clamp(internal_x, 0, 319); + std::clamp(internal_y, 0, 239); + touch_state->touch_pressed = true; + touch_state->touch_x = internal_x; + touch_state->touch_y = internal_y; + return true; +} void EmuWindow::TouchReleased() { std::scoped_lock guard{touch_state->mutex}; touch_state->touch_pressed = false; diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 175f41860..cd2f81b95 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -199,6 +199,9 @@ public: */ bool TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y); + /// Signal a touch pressed event occured, bypassing the framebuffer (e.g. stylus touch button pressed on controller) + bool TouchDirectlyPressed(unsigned internal_x, unsigned internal_y); + /// Signal that a touch released event has occurred (e.g. mouse click released) void TouchReleased(); diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 6204c1d5d..5aedda619 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -24,11 +24,14 @@ #include "core/hle/service/ir/ir_user.h" #include "core/hle/service/service.h" #include "core/movie.h" +#include "hid.h" SERVICE_CONSTRUCT_IMPL(Service::HID::Module) SERIALIZE_EXPORT_IMPL(Service::HID::Module) namespace Service::HID { +std::array Module::stylusInput = {0}; +std::array Module::modButtons = {0}; template void Module::serialize(Archive& ar, const unsigned int file_version) { @@ -223,7 +226,6 @@ void Module::UpdatePadCallback(std::uintptr_t user_data, s64 cycles_late) { state.debug.Assign(buttons[Debug - BUTTON_HID_BEGIN]->GetStatus()); state.gpio14.Assign(buttons[Gpio14 - BUTTON_HID_BEGIN]->GetStatus()); - // Setting up inputs for Cursor Class. float c_stick_x_f, c_stick_y_f; std::tie(c_stick_x_f, c_stick_y_f) = c_stick->GetStatus(); @@ -231,7 +233,9 @@ void Module::UpdatePadCallback(std::uintptr_t user_data, s64 cycles_late) { stylusInput[1] = c_stick_y_f; stylusInput[2] = zl_button->GetStatus(); stylusInput[3] = zr_button->GetStatus(); - // LOG_INFO(Service_HID, "C-Stick X: {}, C-Stick Y: {}, ZL: {}, ZR: {}", stylusInput[0], stylusInput[1], stylusInput[2], stylusInput[3]); + for (int i = 0; i < 12; i++){ + modButtons[i] = buttons[i - BUTTON_HID_BEGIN]->GetStatus(); + } // Get current circle pad position and update circle pad direction float circle_pad_x_f, circle_pad_y_f; @@ -340,6 +344,10 @@ std::array Module::getStylusInputs(){ return stylusInput; } +std::array Module::getModButtons(){ + return modButtons; +} + void Module::UpdateAccelerometerCallback(std::uintptr_t user_data, s64 cycles_late) { SharedMem* mem = reinterpret_cast(shared_mem->GetPointer()); diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 7ef815838..53ef85741 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -335,7 +335,8 @@ public: void UseArticClient(const std::shared_ptr& client); void ReloadInputDevices(); - std::array getStylusInputs(); + static std::array getStylusInputs(); + static std::array getModButtons(); const PadState& GetState() const; // Updating period for each HID device. These empirical values are measured from a 11.2 3DS. @@ -396,7 +397,8 @@ private: std::unique_ptr controller_touch_device; std::unique_ptr touch_device; std::unique_ptr touch_btn_device; - std::array stylusInput = {0}; + static std::array stylusInput; + static std::array modButtons; std::shared_ptr artic_controller; std::shared_ptr artic_client;