Export screen: widescreen frame, black sides, slots can be cleared

- cUI_Character (export) and cUI_Savegame draw inside the centred 1024x768
  frame (viewport) with the render target beside it cleared black; the
  export screen's 3D heroes were stretched across the wide viewport, away
  from their labels and hit areas.
- Export screen shows the game's erase button for an occupied slot and
  reloads the heroes after a delete. Overwriting was already the game's
  own path once clicks hit the right slot.
- game/ui/control.h: shared show/hide for cUI_Control2.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
khallaandClaude Opus 5.5 committed 2026-10-18 23:26:53 +02:00
1 parent c875d7d6b9
commit 72525cd5ba
9 files changed
+272 -13

No files matched your search

+66 -2
View File
@@ -430,6 +430,49 @@ HRESULT __stdcall ClearShifted(void* device, DWORD count, Rect7* rects, DWORD fl
return g_clear(device, count, shifted.data(), flags, color, z, stencil);
}
using GetViewportFn = HRESULT(__stdcall*)(void* device, Viewport7* viewport);
constexpr int kVtableGetViewport = 15; // IDirect3DDevice7 (13 SetViewport, 14 MultiplyTransform)
constexpr DWORD kClearTarget = 1; // D3DCLEAR_TARGET
// RAII for full-screen windows (character export, save games) that fill the
// 1024 frame with a picture: the render target left and right of the frame is
// cleared black, and the window draws with a viewport on the frame. The
// export screen's 3D scene (heroes, background) uses a projection for a
// 1024x768 viewport; across the widened one it was stretched 1.8x wide, away
// from the slot labels and hit areas (measured on a screenshot, 2026-10-18).
// Clear clips to the viewport, so the sides are cleared before it is set.
class FrameScope {
public:
FrameScope(void* window, void* device, int dx) {
const auto* w = static_cast<const uint8_t*>(window);
if (dx == 0 || g_drawScoped || !device || !g_setViewport || !g_clear || (w[kControlFlags] & 1) == 0) {
return;
}
auto getViewport = reinterpret_cast<GetViewportFn>((*static_cast<void***>(device))[kVtableGetViewport]);
if (FAILED(getViewport(device, &previous_))) {
return;
}
const LONG height = static_cast<LONG>(previous_.height);
Rect7 sides[] = {{0, 0, dx, height},
{dx + kLayoutWidth, 0, mods::widescreen::RenderWidth(), height}};
g_clear(device, 2, sides, kClearTarget, 0, 1.0f, 0);
Viewport7 frame = {static_cast<DWORD>(dx), 0, kLayoutWidth, 768, previous_.minZ, previous_.maxZ};
device_ = device;
g_setViewport(device, &frame);
}
~FrameScope() {
if (device_) {
g_setViewport(device_, &previous_);
}
}
FrameScope(const FrameScope&) = delete;
FrameScope& operator=(const FrameScope&) = delete;
private:
void* device_ = nullptr;
Viewport7 previous_ = {};
};
} // namespace
int CursorOffsetX() {
@@ -571,6 +614,27 @@ int TooltipOffset(void* tooltip) {
return name##_original(self, edx, device, a, b); \
}
// Centred full-screen windows: black beside the frame, viewport on the frame.
#define HUD_RENDER_FRAMED(address, name) \
int __fastcall name(void* self, void* edx, void* device); \
RE_HOOK(address, name); \
int __fastcall name(void* self, void* edx, void* device) { \
const int dx = mods::hud::OffsetFor(mods::hud::Anchor::Center); \
mods::hud::FrameScope frame(self, device, dx); \
mods::hud::DrawOffset offset(self, dx); \
return name##_original(self, edx, device); \
}
#define HUD_RENDER3_FRAMED(address, name) \
int __fastcall name(void* self, void* edx, void* device, int a, int b); \
RE_HOOK(address, name); \
int __fastcall name(void* self, void* edx, void* device, int a, int b) { \
const int dx = mods::hud::OffsetFor(mods::hud::Anchor::Center); \
mods::hud::FrameScope frame(self, device, dx); \
mods::hud::DrawOffset offset(self, dx); \
return name##_original(self, edx, device, a, b); \
}
namespace game {
using mods::hud::Anchor;
@@ -629,11 +693,11 @@ HUD_RENDER(0x006ea750, UiTrade_Render, Anchor::Center)
HUD_EVENT(0x006f8960, UiNetworkInfo_HandleEvent, Anchor::Center)
HUD_RENDER(0x006f73f0, UiNetworkInfo_Render, Anchor::Center)
HUD_EVENT(0x0070e860, UiCharacter_HandleEvent, Anchor::Center)
HUD_RENDER3(0x0070f160, UiCharacter_Render, Anchor::Center)
HUD_RENDER3_FRAMED(0x0070f160, UiCharacter_Render) // export screen in-game
HUD_EVENT(0x00716b30, UiOptions_HandleEvent, Anchor::Center)
HUD_RENDER3(0x00716640, UiOptions_Render, Anchor::Center)
HUD_EVENT(0x0071d130, UiSavegame_HandleEvent, Anchor::Center)
HUD_RENDER(0x0071dd80, UiSavegame_Render, Anchor::Center)
HUD_RENDER_FRAMED(0x0071dd80, UiSavegame_Render)
HUD_EVENT(0x00720c10, UiBusyDlg_HandleEvent, Anchor::Center)
HUD_RENDER(0x007213a0, UiBusyDlg_Render, Anchor::Center)