Quest dialogs: stay centred, clickable, closable with Esc
- Quest dialogs are tooltips. A tooltip now keeps the offset it gets when it becomes visible (the hovered window's, or centred for dialogs) and records it for hit tests; its own event handler is shifted too. Hidden windows no longer count as hovered. - Esc closes a visible dialog the way its answer buttons do: hide, then cDialogManager::Answer with no answer (ends the input lock, nothing chosen). - Autotest can select a save game row (-Save). - Names for the UI manager, tooltip and dialog functions. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
1fd89524d3
commit
a7f8496b2b
6 files changed
+165
-4
No files matched your search
+98
-2
@@ -159,6 +159,20 @@ volatile LONG g_hoverOffset = 0;
|
||||
volatile DWORD g_hoverTime = 0;
|
||||
constexpr DWORD kHoverExpiryMs = 150;
|
||||
|
||||
int HoverOffset();
|
||||
|
||||
// Tooltips seen so far: whether visible, and the offset chosen when they
|
||||
// became visible.
|
||||
struct TooltipState {
|
||||
void* tooltip;
|
||||
bool visible;
|
||||
bool dialog;
|
||||
int dx;
|
||||
};
|
||||
constexpr int kTooltipCount = 64;
|
||||
TooltipState g_tooltips[kTooltipCount] = {};
|
||||
constexpr size_t kControlFlags = 0x10; // bit 0: visible
|
||||
|
||||
int HoverOffset() {
|
||||
return GetTickCount() - g_hoverTime < kHoverExpiryMs ? g_hoverOffset : 0;
|
||||
}
|
||||
@@ -170,6 +184,9 @@ void TrackHover(void* window, int dx) {
|
||||
return;
|
||||
}
|
||||
const auto* w = static_cast<const uint8_t*>(window);
|
||||
if ((w[0x10] & 1) == 0) {
|
||||
return; // hidden windows render too (and some cover the whole frame)
|
||||
}
|
||||
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);
|
||||
@@ -400,6 +417,64 @@ void OnDevice(void* device) {
|
||||
}
|
||||
}
|
||||
|
||||
// Dialogs among the tooltips: visible and at least kDialogMinWidth wide (the
|
||||
// quest dialog: 752; hover tooltips and speech bubbles are narrow). The
|
||||
// hover rule above cannot tell: a full-screen window drawn while hidden counts
|
||||
// as hovered whenever the cursor is inside the 1024 frame.
|
||||
constexpr int kDialogMinWidth = 400;
|
||||
|
||||
int VisibleDialogTooltipsImpl(void** out, int max) {
|
||||
int count = 0;
|
||||
for (const TooltipState& t : g_tooltips) {
|
||||
if (!t.tooltip) {
|
||||
continue;
|
||||
}
|
||||
const auto* p = static_cast<const uint8_t*>(t.tooltip);
|
||||
if ((p[kControlFlags] & 1) == 0) {
|
||||
continue;
|
||||
}
|
||||
const int width = *reinterpret_cast<const uint16_t*>(p + 0x2c);
|
||||
if (width >= kDialogMinWidth && count < max) {
|
||||
out[count++] = t.tooltip;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
int KnownTooltipOffset(void* tooltip) {
|
||||
for (const TooltipState& t : g_tooltips) {
|
||||
if (t.tooltip == tooltip) {
|
||||
return t.dx;
|
||||
}
|
||||
}
|
||||
return OffsetFor(Anchor::Center);
|
||||
}
|
||||
|
||||
int TooltipOffset(void* tooltip) {
|
||||
TooltipState* state = nullptr;
|
||||
for (TooltipState& t : g_tooltips) {
|
||||
if (t.tooltip == tooltip) {
|
||||
state = &t;
|
||||
break;
|
||||
}
|
||||
if (!state && !t.tooltip) {
|
||||
state = &t;
|
||||
}
|
||||
}
|
||||
if (!state) {
|
||||
return HoverOffset();
|
||||
}
|
||||
const bool visible = (static_cast<uint8_t*>(tooltip)[kControlFlags] & 1) != 0;
|
||||
if (state->tooltip != tooltip || (visible && !state->visible)) {
|
||||
const int hover = HoverOffset();
|
||||
state->tooltip = tooltip;
|
||||
state->dialog = hover == 0;
|
||||
state->dx = hover != 0 ? hover : OffsetFor(Anchor::Center);
|
||||
}
|
||||
state->visible = visible;
|
||||
return state->dx;
|
||||
}
|
||||
|
||||
} // namespace mods::hud
|
||||
|
||||
// --- HUD windows ---------------------------------------------------------------
|
||||
@@ -516,11 +591,28 @@ HUD_EVENT(0x00753360, UiStatic_HandleEvent, Anchor::Center)
|
||||
HUD_RENDER(0x007530a0, UiStatic_Render, Anchor::Center)
|
||||
HUD_RENDER(0x007228b0, UiTextLines_Render, Anchor::Center)
|
||||
|
||||
// Tooltips: placed by the hovered window in its own layout (see TrackHover).
|
||||
// Tooltips (cUI_Tooltip, a pool at cUI_Manager +0x124, one panel each). A
|
||||
// tooltip is either a hover tooltip placed by the window under the cursor in
|
||||
// its own layout, or a dialog of its own, e.g. a quest dialog (laid out in the
|
||||
// 1024 frame, measured: rect 136,306 752x157). When a tooltip becomes visible
|
||||
// it keeps an offset until it is hidden: the hovered window's (TrackHover) if
|
||||
// a moved window is under the cursor, otherwise the centre (a dialog). Taking
|
||||
// the hover offset on every frame made dialogs jump to the 1024 frame when
|
||||
// the cursor left the UI, and without a recorded offset their hit tests used
|
||||
// none, so clicks only worked at the unshifted position.
|
||||
// Its event handler (vf10 0x6e7120) tests the click itself before it calls
|
||||
// the shared window handler: it needs the same offset as its drawing.
|
||||
int __fastcall UiTooltip_HandleEvent(void* self, void* edx, void* event);
|
||||
RE_HOOK(0x006e7120, UiTooltip_HandleEvent);
|
||||
int __fastcall UiTooltip_HandleEvent(void* self, void* edx, void* event) {
|
||||
mods::hud::EventOffset offset(event, mods::hud::KnownTooltipOffset(self));
|
||||
return UiTooltip_HandleEvent_original(self, edx, event);
|
||||
}
|
||||
|
||||
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());
|
||||
mods::hud::DrawOffset offset(self, mods::hud::TooltipOffset(self));
|
||||
return UiTooltip_Render_original(self, edx, device);
|
||||
}
|
||||
|
||||
@@ -544,6 +636,10 @@ int HitTestX(void* window, int x) {
|
||||
return x - OffsetOf(root);
|
||||
}
|
||||
|
||||
int VisibleDialogTooltips(void** out, int max) {
|
||||
return VisibleDialogTooltipsImpl(out, max);
|
||||
}
|
||||
|
||||
} // namespace mods::hud
|
||||
|
||||
namespace game {
|
||||
|
||||
Reference in new issue
Block a user