Files
spice2x.github.io/src/spice2x/touch/native/transform.cpp
T
bicarus 09ed8b948c nost: move nostalgia from wintouchemu to native touch hook (#827)
## Link to GitHub Issue or related Pull Request, if one exists
#814

## Description of change
Switch over Nostalgia from wintouchemu to native touch hook.

Nostalgia has some strict timing requirements (touches must be updated
on every acio poll) so this takes a slightly different path to maintain
mouse holds.

Only a handful of consumers of wintouchemu remain:

1. beatstream - but it's off by default, only enabled as errata for
buggy touchscreens, or if the user forces it on (for Show Cursor)
2. gitadora single-window overlay - this is just for mouse so not a big
deal.
3. MFC HG mode - not a priority to fix.

## Testing
Tested full screen and windowed mode with touch / mouse / poke.
2026-07-22 00:56:03 -07:00

142 lines
5.3 KiB
C++

#include "transform.h"
#include "hooks/graphics/graphics.h"
#include "overlay/overlay.h"
#include "settings.h"
#include "touch/touch.h"
namespace nativetouch::transform {
static bool game_client_to_screen(HWND window, POINT *position) {
RECT client_rect {};
if (window == nullptr ||
!GetClientRect(window, &client_rect) ||
client_rect.right <= 0 || client_rect.bottom <= 0 ||
!PtInRect(&client_rect, *position)) {
return false;
}
return ClientToScreen(window, position) != FALSE;
}
static bool screen_to_game_client(HWND window, POINT *position) {
RECT client_rect {};
if (window == nullptr ||
!GetClientRect(window, &client_rect) ||
client_rect.right <= 0 || client_rect.bottom <= 0 ||
!ScreenToClient(window, position) ||
!PtInRect(&client_rect, *position)) {
return false;
}
return true;
}
bool is_tdj_dedicated_subscreen(HWND window) {
return window != nullptr && GRAPHICS_WINDOWED && GRAPHICS_IIDX_WSUB &&
window == TDJ_SUBSCREEN_WINDOW;
}
// convert game touch coordinates to Windows desktop coordinates
bool game_to_screen(HWND window, POINT *position) {
if (settings::SYNTHETIC_TOUCH_USES_CLIENT_COORDINATES) {
return game_client_to_screen(window, position);
}
if (!is_tdj_dedicated_subscreen(window)) {
return true;
}
RECT client_rect {};
if (!GetClientRect(window, &client_rect) ||
client_rect.right <= 0 || client_rect.bottom <= 0 ||
SPICETOUCH_TOUCH_WIDTH <= 0 || SPICETOUCH_TOUCH_HEIGHT <= 0) {
return false;
}
position->x = MulDiv(
position->x - SPICETOUCH_TOUCH_X,
client_rect.right,
SPICETOUCH_TOUCH_WIDTH);
position->y = MulDiv(
position->y - SPICETOUCH_TOUCH_Y,
client_rect.bottom,
SPICETOUCH_TOUCH_HEIGHT);
return ClientToScreen(window, position) != FALSE;
}
static bool has_active_overlay_transform() {
return overlay::OVERLAY != nullptr &&
overlay::OVERLAY->get_active() &&
overlay::OVERLAY->can_transform_touch_input();
}
static bool transform_overlay_touch_position(POINT *position) {
// convert physical screen coordinates to the window-relative coordinates the overlay expects
if (GRAPHICS_WINDOWED) {
position->x -= SPICETOUCH_TOUCH_X;
position->y -= SPICETOUCH_TOUCH_Y;
}
// ask the overlay to do the game-specific translation
return overlay::OVERLAY->transform_touch_point(&position->x, &position->y);
}
// convert physical screen coordinates to game touch coordinates for a known target
bool screen_to_game(HWND window, POINT *position) {
if (settings::SYNTHETIC_TOUCH_USES_CLIENT_COORDINATES) {
return screen_to_game_client(window, position);
}
// scale the resized IIDX subscreen client area into the game's touch-display coordinates
if (is_tdj_dedicated_subscreen(window)) {
RECT client_rect {};
if (!GetClientRect(window, &client_rect) ||
client_rect.right <= 0 || client_rect.bottom <= 0 ||
SPICETOUCH_TOUCH_WIDTH <= 0 || SPICETOUCH_TOUCH_HEIGHT <= 0) {
return false;
}
if (!ScreenToClient(window, position)) {
return false;
}
if (!PtInRect(&client_rect, *position)) {
return false;
}
position->x = SPICETOUCH_TOUCH_X +
MulDiv(position->x, SPICETOUCH_TOUCH_WIDTH, client_rect.right);
position->y = SPICETOUCH_TOUCH_Y +
MulDiv(position->y, SPICETOUCH_TOUCH_HEIGHT, client_rect.bottom);
return true;
}
// check if subscreen overlay is active and can transform the touch point;
// if not, the touch point is valid as-is
if (!has_active_overlay_transform()) {
return true;
}
// ask the overlay to transform the touch point into game coordinates
return transform_overlay_touch_position(position);
}
// route hardware screen coordinates through dedicated or overlay mapping and report the result
Result hardware_to_game(POINT *position) {
const auto dedicated_subscreen = is_tdj_dedicated_subscreen(TDJ_SUBSCREEN_WINDOW);
const auto active_overlay = has_active_overlay_transform();
// no dedicated subscreen or active overlay mapping; pass the point through unchanged
if (!dedicated_subscreen && !active_overlay) {
return Result::Unchanged;
}
// route through the dedicated subscreen when active, otherwise through the overlay
const auto valid = screen_to_game(
dedicated_subscreen ? TDJ_SUBSCREEN_WINDOW : nullptr,
position);
// reject out-of-bounds points and any coordinate conversion failure
return valid ? Result::Transformed : Result::Rejected;
}
}