Files
spice2x.github.io/src/spice2x/overlay/windows/generic_sub.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

263 lines
10 KiB
C++

#undef CINTERFACE
#include "generic_sub.h"
#include "avs/game.h"
#include "games/io.h"
#include "cfg/screen_resize.h"
#include "hooks/graphics/backends/d3d9/d3d9_backend.h"
#include "hooks/graphics/backends/d3d9/d3d9_device.h"
#include "hooks/graphics/graphics.h"
#include "util/logging.h"
#include "util/utils.h"
#include "touch/touch.h"
int GENERIC_SUB_WINDOW_X = 0;
int GENERIC_SUB_WINDOW_Y = 0;
int GENERIC_SUB_WINDOW_WIDTH = 0;
int GENERIC_SUB_WINDOW_HEIGHT = 0;
bool GENERIC_SUB_WINDOW_FULLSIZE = false;
// #define OVERLAYDBG 1
namespace overlay::windows {
const ImVec4 YELLOW(1.f, 1.f, 0.f, 1.f);
const ImVec4 WHITE(1.f, 1.f, 1.f, 1.f);
GenericSubScreen::GenericSubScreen(SpiceOverlay *overlay) : Window(overlay), device(overlay->get_device()) {
this->remove_window_padding = true;
// ImGuiWindowFlags_NoBackground is needed as the background is drawn on top of the subscreen image
this->flags = ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoBackground |
ImGuiWindowFlags_NoDocking;
this->size_max = ImVec2(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y);
this->size_min = ImVec2(240, 135 + ImGui::GetFrameHeight());
this->init_size = size_min;
this->resize_callback = keep_16_by_9;
this->toggle_button = games::OverlayButtons::ToggleSubScreen;
this->texture_width = 0;
this->texture_height = 0;
overlay->set_subscreen_touch_transform([this](LONG *x, LONG *y) -> bool {
// convert to normalized form (relative window coordinates 0.f-1.f)
ImVec2 xy;
// log_misc("sub::overlay", "mouse handler {} {}", to_string(*x), to_string(*y));
// log_misc("sub::overlay", "spicetouch coords {} {} {} {}", to_string(SPICETOUCH_TOUCH_X), to_string(SPICETOUCH_TOUCH_Y), to_string(SPICETOUCH_TOUCH_WIDTH), to_string(SPICETOUCH_TOUCH_HEIGHT));
float ratio_x, ratio_y;
if (GRAPHICS_WINDOWED) {
// input coords are relative to spicetouch wnd
ratio_x = (float)*x / SPICETOUCH_TOUCH_WIDTH;
ratio_y = (float)*y / SPICETOUCH_TOUCH_HEIGHT;
} else {
// inputs coords are relative to (0,0) for non-windowed mode
ratio_x = (float)*x / ImGui::GetIO().DisplaySize.x;
ratio_y = (float)*y / ImGui::GetIO().DisplaySize.y;
}
// log_misc("sub::overlay", "game coords {} {}", to_string(ratio_x), to_string(ratio_y));
// transform to subscreen overlay coords
if (!GENERIC_SUB_WINDOW_FULLSIZE) {
ratio_x = (ratio_x * ImGui::GetIO().DisplaySize.x - GENERIC_SUB_WINDOW_X) / GENERIC_SUB_WINDOW_WIDTH;
ratio_y = (ratio_y * ImGui::GetIO().DisplaySize.y - GENERIC_SUB_WINDOW_Y) / GENERIC_SUB_WINDOW_HEIGHT;
// log_misc("sub::overlay", "overlay coords {} {} {} {}", to_string(GENERIC_SUB_WINDOW_X), to_string(GENERIC_SUB_WINDOW_Y), to_string(GENERIC_SUB_WINDOW_WIDTH), to_string(GENERIC_SUB_WINDOW_HEIGHT));
}
xy.x = ratio_x;
xy.y = ratio_y;
// log_misc("sub::overlay", "ratio {} {}", to_string(xy.x), to_string(xy.y));
// x/y can be outside of window
if (xy.x < 0.f || 1.f < xy.x || xy.y < 0.f || 1.f < xy.y) {
return false;
}
// call into child
this->touch_transform(xy, x, y);
return true;
});
}
void GenericSubScreen::touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) {}
void GenericSubScreen::check_for_errors() {}
void GenericSubScreen::build_content() {
this->flags |= ImGuiWindowFlags_NoBackground;
check_for_errors();
if (this->disabled_message.has_value()) {
this->draws_window = true;
this->flags &= ~ImGuiWindowFlags_NoBackground;
ImGui::TextColored(YELLOW, "%s", this->disabled_message.value().c_str());
ImGui::TextUnformatted("");
if (ImGui::Button("Close")) {
this->set_active(false);
}
return;
}
this->draw_texture();
if (!this->texture) {
this->draws_window = true;
this->flags &= ~ImGuiWindowFlags_NoBackground;
ImGui::TextColored(YELLOW, "Failed to acquire surface texture for subscreen.");
if (avs::game::is_model("LDJ")) {
ImGui::TextColored(
YELLOW,
"Ensure that you are using the correct DLL type, \n"
"or patch the DLL to properly enable TDJ mode.");
} else if (avs::game::is_model("KFC")) {
ImGui::TextColored(
YELLOW,
"Ensure that you did not accidentally enable a patch \n"
"that turns off subscreen rendering.");
}
ImGui::TextUnformatted("");
if (ImGui::Button("Close")) {
this->set_active(false);
}
}
#if OVERLAYDBG
if (this->status_message.has_value()) {
log_warning("sub::overlay", "{}", this->status_message.value().c_str());
}
if (this->status_message.has_value()) {
ImGui::TextColored(YELLOW, "%s", this->status_message.value().c_str());
} else if (this->texture) {
ImGui::TextColored(WHITE, "Successfully acquired surface texture");
} else {
ImGui::TextColored(YELLOW, "Failed to acquire surface texture");
}
#endif
}
bool GenericSubScreen::build_texture(IDirect3DSurface9 *surface, UINT width, UINT height) {
HRESULT hr;
D3DSURFACE_DESC desc {};
hr = surface->GetDesc(&desc);
if (FAILED(hr)) {
this->status_message = fmt::format("Failed to get surface descriptor, hr={}", FMT_HRESULT(hr));
return false;
}
hr = this->device->CreateTexture(width, height, 0, desc.Usage, desc.Format,
desc.Pool, &this->texture, nullptr);
if (FAILED(hr)) {
this->status_message = fmt::format("Failed to create render target, hr={}", FMT_HRESULT(hr));
return false;
}
this->texture_width = width;
this->texture_height = height;
return true;
}
void GenericSubScreen::draw_texture() {
HRESULT hr;
auto surface = graphics_d3d9_ldj_get_sub_screen();
if (surface == nullptr) {
return;
}
if (this->draws_window) {
// calculate the **content** location and size.
// GetContentRegionAvail returns the correct dimension, up to (and including) the resize handle
// don't be tempted to change it to some other ImGui routine that accomplishes similar things!
overlay_content_top_left = ImGui::GetCursorScreenPos();
overlay_content_size = ImGui::GetContentRegionAvail();
} else {
// no window, full screen
overlay_content_top_left = ImVec2(0, 0);
overlay_content_size = ImGui::GetIO().DisplaySize;
}
GENERIC_SUB_WINDOW_X = overlay_content_top_left.x;
GENERIC_SUB_WINDOW_Y = overlay_content_top_left.y;
GENERIC_SUB_WINDOW_WIDTH = overlay_content_size.x;
GENERIC_SUB_WINDOW_HEIGHT = overlay_content_size.y;
if (this->draws_window &&
this->texture &&
((UINT)overlay_content_size.x != this->texture_width)) {
#if OVERLAYDBG
log_info("sub::overlay", "resize {} != {} ", overlay_content_size.x, this->texture_width);
#endif
// hack needed for SDVX; resizing the texture results in darker image, so allocate texture
// again with the new size when the window is resized
this->texture->Release();
this->texture = nullptr;
this->texture_width = 0;
this->texture_height = 0;
}
if (this->texture == nullptr) {
if (!this->build_texture(surface, overlay_content_size.x, overlay_content_size.y)) {
this->texture = nullptr;
this->texture_width = 0;
this->texture_height = 0;
surface->Release();
return;
}
}
IDirect3DSurface9 *texture_surface = nullptr;
hr = this->texture->GetSurfaceLevel(0, &texture_surface);
if (FAILED(hr)) {
this->status_message = fmt::format("Failed to get texture surface, hr={}", FMT_HRESULT(hr));
surface->Release();
return;
}
hr = this->device->StretchRect(surface, nullptr, texture_surface, nullptr, D3DTEXF_LINEAR);
if (FAILED(hr)) {
this->status_message = fmt::format("Failed to copy back buffer contents, hr={}", FMT_HRESULT(hr));
surface->Release();
texture_surface->Release();
return;
}
surface->Release();
texture_surface->Release();
// draw the subscreen into the current window's draw list so it participates in
// normal ImGui z-ordering (i.e. other ImGui windows can appear above it)
auto bottom_right = overlay_content_size;
bottom_right.x += overlay_content_top_left.x;
bottom_right.y += overlay_content_top_left.y;
auto draw_list = this->draws_window
? ImGui::GetWindowDrawList()
: ImGui::GetBackgroundDrawList();
draw_list->AddImage(
reinterpret_cast<ImTextureID>(this->texture),
overlay_content_top_left,
bottom_right);
if (this->draws_window) {
// draw an invisible button so that it swallows mouse input
// this is needed to prevent the window from being dragged around
// (alternatively io.ConfigWindowsMoveFromTitleBarOnly can be set but that is global)
ImGui::InvisibleButton(
(this->title + "__InvisibleButton").c_str(),
overlay_content_size,
ImGuiButtonFlags_None);
}
#if OVERLAYDBG
log_info("sub::overlay", "{} / {} = {}", overlay_content_size.x, overlay_content_size.y, overlay_content_size.x / overlay_content_size.y);
#endif
}
}