HUD: hover and tooltips for moved windows
Windows poll the mouse cursor object (0xcdbadc, x +4) for hover; it is shifted inside a moved window's render/event scope like the events. Tooltips are separate windows placed by the hovered window in its 1024 layout, so cUI_Tooltip renders with the offset of the moved window under the cursor. Adds Move-To to the test input helpers. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
a640f2fb17
commit
2853f82627
3 files changed
+103
-7
No files matched your search
@@ -26,6 +26,7 @@ constexpr uint32_t WorldToScreen = 0x00623c40;
|
|||||||
// --- globals -----------------------------------------------------------------
|
// --- globals -----------------------------------------------------------------
|
||||||
constexpr uint32_t g_dxDriver = 0x00cda99c; // DxDriver7*
|
constexpr uint32_t g_dxDriver = 0x00cda99c; // DxDriver7*
|
||||||
constexpr uint32_t g_appFlags = 0x0182cdb8; // uint32_t, 0x20000 = inactive
|
constexpr uint32_t g_appFlags = 0x0182cdb8; // uint32_t, 0x20000 = inactive
|
||||||
|
constexpr uint32_t g_mouseCursor = 0x00cdbadc; // mouse cursor object*: x +4, y +8 (render pixels)
|
||||||
|
|
||||||
// --- read-only constants -----------------------------------------------------
|
// --- read-only constants -----------------------------------------------------
|
||||||
constexpr uint32_t kScreenWidthF = 0x0088e6f4; // float 1024.0
|
constexpr uint32_t kScreenWidthF = 0x0088e6f4; // float 1024.0
|
||||||
|
|||||||
+92
-7
@@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include "game/addresses.h"
|
||||||
#include "game/render/dx_driver7.h"
|
#include "game/render/dx_driver7.h"
|
||||||
#include "mods/widescreen.h"
|
#include "mods/widescreen.h"
|
||||||
#include "re/config.h"
|
#include "re/config.h"
|
||||||
@@ -53,19 +54,94 @@ int OffsetFor(Anchor anchor) {
|
|||||||
return anchor == Anchor::Center ? (width - kLayoutWidth) / 2 : width - kLayoutWidth;
|
return anchor == Anchor::Center ? (width - kLayoutWidth) / 2 : width - kLayoutWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Whether the current thread has already shifted the mouse cursor position.
|
||||||
|
thread_local bool g_cursorShifted = false;
|
||||||
|
|
||||||
|
// RAII: shift the mouse cursor object's x by -dx for the scope (outermost
|
||||||
|
// window only). Windows poll it for hover (highlights, tooltips) while they
|
||||||
|
// render and handle events. The window thread may store a new position
|
||||||
|
// meanwhile; that one is kept instead of restoring.
|
||||||
|
class CursorOffset {
|
||||||
|
public:
|
||||||
|
explicit CursorOffset(int dx) {
|
||||||
|
auto* cursor = *reinterpret_cast<uint8_t**>(game::addr::g_mouseCursor);
|
||||||
|
if (dx == 0 || g_cursorShifted || !cursor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
x_ = reinterpret_cast<volatile int32_t*>(cursor + 4);
|
||||||
|
original_ = *x_;
|
||||||
|
shifted_ = original_ - dx;
|
||||||
|
*x_ = shifted_;
|
||||||
|
g_cursorShifted = true;
|
||||||
|
}
|
||||||
|
~CursorOffset() {
|
||||||
|
if (x_) {
|
||||||
|
InterlockedCompareExchange(reinterpret_cast<volatile LONG*>(x_), original_, shifted_);
|
||||||
|
g_cursorShifted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
volatile int32_t* x_ = nullptr;
|
||||||
|
int32_t original_ = 0;
|
||||||
|
int32_t shifted_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Whether the current thread is inside a hooked window's render method.
|
||||||
|
thread_local bool g_drawScoped = false;
|
||||||
|
|
||||||
|
// Offset of the moved window under the cursor, noted while the windows render
|
||||||
|
// (expires when no moved window is hovered any more). Tooltips are separate
|
||||||
|
// windows positioned by the hovered window in its own layout, so they take its
|
||||||
|
// offset.
|
||||||
|
volatile LONG g_hoverOffset = 0;
|
||||||
|
volatile DWORD g_hoverTime = 0;
|
||||||
|
constexpr DWORD kHoverExpiryMs = 150;
|
||||||
|
|
||||||
|
int HoverOffset() {
|
||||||
|
return GetTickCount() - g_hoverTime < kHoverExpiryMs ? g_hoverOffset : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notes the window's offset if the cursor is over it (screen position).
|
||||||
|
void TrackHover(void* window, int dx) {
|
||||||
|
auto* cursor = *reinterpret_cast<uint8_t**>(game::addr::g_mouseCursor);
|
||||||
|
if (dx == 0 || !window || !cursor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto* w = static_cast<const uint8_t*>(window);
|
||||||
|
const int x = *reinterpret_cast<const int32_t*>(w + 0x24) + dx;
|
||||||
|
const int y = *reinterpret_cast<const int32_t*>(w + 0x28);
|
||||||
|
const int width = *reinterpret_cast<const uint16_t*>(w + 0x2c);
|
||||||
|
const int height = *reinterpret_cast<const uint16_t*>(w + 0x2e);
|
||||||
|
const int cx = *reinterpret_cast<const int32_t*>(cursor + 4);
|
||||||
|
const int cy = *reinterpret_cast<const int32_t*>(cursor + 8);
|
||||||
|
if (cx >= x && cx < x + width && cy >= y && cy < y + height) {
|
||||||
|
g_hoverOffset = dx;
|
||||||
|
g_hoverTime = GetTickCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// RAII: shift drawing by dx for the scope. Windows nest (children, shared base
|
// RAII: shift drawing by dx for the scope. Windows nest (children, shared base
|
||||||
// methods), so only the outermost window decides.
|
// methods), so only the outermost window decides.
|
||||||
class DrawOffset {
|
class DrawOffset {
|
||||||
public:
|
public:
|
||||||
explicit DrawOffset(int dx) : previous_(g_offsetX) {
|
DrawOffset(void* window, int dx) : outermost_(!g_drawScoped), cursor_(outermost_ ? dx : 0) {
|
||||||
if (g_offsetX == 0) {
|
if (outermost_) {
|
||||||
|
TrackHover(window, dx);
|
||||||
|
g_drawScoped = true;
|
||||||
g_offsetX = dx;
|
g_offsetX = dx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
~DrawOffset() { g_offsetX = previous_; }
|
~DrawOffset() {
|
||||||
|
if (outermost_) {
|
||||||
|
g_drawScoped = false;
|
||||||
|
g_offsetX = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int previous_;
|
bool outermost_;
|
||||||
|
CursorOffset cursor_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mouse events (cEvent_mouseMove / _mouseButton_down / _mouseButton_up): vf04
|
// Mouse events (cEvent_mouseMove / _mouseButton_down / _mouseButton_up): vf04
|
||||||
@@ -84,7 +160,7 @@ int32_t* MouseEventX(void* event) {
|
|||||||
// RAII: shift the mouse position by -dx for the scope (outermost window only).
|
// RAII: shift the mouse position by -dx for the scope (outermost window only).
|
||||||
class EventOffset {
|
class EventOffset {
|
||||||
public:
|
public:
|
||||||
EventOffset(void* event, int dx) {
|
EventOffset(void* event, int dx) : cursor_(dx) {
|
||||||
if (dx != 0 && !g_eventShifted) {
|
if (dx != 0 && !g_eventShifted) {
|
||||||
x_ = MouseEventX(event);
|
x_ = MouseEventX(event);
|
||||||
if (x_) {
|
if (x_) {
|
||||||
@@ -104,6 +180,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
int32_t* x_ = nullptr;
|
int32_t* x_ = nullptr;
|
||||||
int dx_ = 0;
|
int dx_ = 0;
|
||||||
|
CursorOffset cursor_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Direct3D 7 device hooks -------------------------------------------------
|
// --- Direct3D 7 device hooks -------------------------------------------------
|
||||||
@@ -209,7 +286,7 @@ void OnDevice(void* device) {
|
|||||||
int __fastcall name(void* self, void* edx, void* device); \
|
int __fastcall name(void* self, void* edx, void* device); \
|
||||||
RE_HOOK(address, name); \
|
RE_HOOK(address, name); \
|
||||||
int __fastcall name(void* self, void* edx, void* device) { \
|
int __fastcall name(void* self, void* edx, void* device) { \
|
||||||
mods::hud::DrawOffset offset(mods::hud::OffsetFor(anchor)); \
|
mods::hud::DrawOffset offset(self, mods::hud::OffsetFor(anchor)); \
|
||||||
return name##_original(self, edx, device); \
|
return name##_original(self, edx, device); \
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,7 +294,7 @@ void OnDevice(void* device) {
|
|||||||
int __fastcall name(void* self, void* edx, void* device, int a, int b); \
|
int __fastcall name(void* self, void* edx, void* device, int a, int b); \
|
||||||
RE_HOOK(address, name); \
|
RE_HOOK(address, name); \
|
||||||
int __fastcall name(void* self, void* edx, void* device, int a, int b) { \
|
int __fastcall name(void* self, void* edx, void* device, int a, int b) { \
|
||||||
mods::hud::DrawOffset offset(mods::hud::OffsetFor(anchor)); \
|
mods::hud::DrawOffset offset(self, mods::hud::OffsetFor(anchor)); \
|
||||||
return name##_original(self, edx, device, a, b); \
|
return name##_original(self, edx, device, a, b); \
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,4 +366,12 @@ HUD_RENDER(0x0071dd80, UiSavegame_Render, Anchor::Center)
|
|||||||
HUD_EVENT(0x00720c10, UiBusyDlg_HandleEvent, Anchor::Center)
|
HUD_EVENT(0x00720c10, UiBusyDlg_HandleEvent, Anchor::Center)
|
||||||
HUD_RENDER(0x007213a0, UiBusyDlg_Render, Anchor::Center)
|
HUD_RENDER(0x007213a0, UiBusyDlg_Render, Anchor::Center)
|
||||||
|
|
||||||
|
// Tooltips: placed by the hovered window in its own layout (see TrackHover).
|
||||||
|
int __fastcall UiTooltip_Render(void* self, void* edx, void* device);
|
||||||
|
RE_HOOK(0x006e7300, UiTooltip_Render);
|
||||||
|
int __fastcall UiTooltip_Render(void* self, void* edx, void* device) {
|
||||||
|
mods::hud::DrawOffset offset(nullptr, mods::hud::HoverOffset());
|
||||||
|
return UiTooltip_Render_original(self, edx, device);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace game
|
} // namespace game
|
||||||
@@ -75,3 +75,13 @@ function Save-Screen([string]$Path, [int]$Width = 3440, [int]$Height = 1440) {
|
|||||||
$graphics.Dispose(); $bitmap.Dispose()
|
$graphics.Dispose(); $bitmap.Dispose()
|
||||||
$full
|
$full
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Moves the cursor in small steps (the game only sees real mouse movement).
|
||||||
|
function Move-To([int]$X, [int]$Y, [int]$AfterMs = 800) {
|
||||||
|
[SacredRe.Input]::SetCursorPos($X - 20, $Y - 20) | Out-Null
|
||||||
|
for ($i = 1; $i -le 10; $i++) {
|
||||||
|
Start-Sleep -Milliseconds 20
|
||||||
|
[SacredRe.Input]::SetCursorPos($X - 20 + 2 * $i, $Y - 20 + 2 * $i) | Out-Null
|
||||||
|
}
|
||||||
|
Start-Sleep -Milliseconds $AfterMs
|
||||||
|
}
|
||||||
Reference in new issue
Block a user