Files
spice2x.github.io/src/spice2x/touch/native/transform.cpp
T
bicarus 7ccd938f9b overlay: mouse-as-touch needs to check if subscreen window is active (#842)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #840 

## Description of change
Currently, for subscreen games, mouse events are still delivered even
when the subscreen overlay window is not visible.

Change this so that the subscreen overlay must be active and under the
mouse cursor for the mouse-to-touch transformation to occur. Applies to
both native and wintouchemu.

## Testing
Needs to test everything again..

iidx:

- [ ]  full screen with overlay
- [ ]  single window with overlay
- [ ]  two window

Test: mouse, poke, api, real touch screen

sdvx:

- [ ] full screen with overlay
- [ ] windowed

popn

- [x] full screen with overlay
- [x] window with overlay
- [x] dedicated window

gitadora

- [x] single window with overlay
- [x] dedicated sub window

nostalgia

- [x] fullscreen
- [x] windowed

test: poke

wintouchemu
- [ ] Do all of the above again with wintouchemu
2026-07-28 01:14:47 -07:00

161 lines
6.0 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->has_subscreen_touch_transform();
}
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);
}
bool mouse_to_game(HWND window, POINT *position) {
// exception: iidx tdj dedicated subscreen window is allowed
if (is_tdj_dedicated_subscreen(window)) {
return screen_to_game(window, position);
}
// if this game has a subscreen overlay that can transform touch input
// but the window is hidden or not under the cursor, reject mouse-as-touch
// (e.g., iidx/sdvx are rejected here, but nostalgia is allowed)
if (overlay::OVERLAY != nullptr &&
overlay::OVERLAY->has_subscreen_touch_transform() &&
!overlay::OVERLAY->accepts_subscreen_mouse_input()) {
return false;
}
return screen_to_game(window, 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;
}
}