railroader-setons-special-s.../native/src/popout_window.h
seton 9f2097e579 Map: view presets, waypoint pins, and TrackLabelService
Named camera bookmarks, Auto Engineer / WaypointQueue pins, and
extracted track labels. Stock MapWindow stays collapsed while S3
owns the camera. Radio Runner is not in this drop.
2026-09-11 15:19:23 -04:00

207 lines
11 KiB
C++

#pragma once
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <d3d11.h>
#include <atomic>
#include <cstdint>
#include <mutex>
#include <cstring>
#include <vector>
#include "input_queue.h"
struct PopoutWindow {
HWND hwnd = nullptr;
HWND contentHwnd = nullptr; // D3D11 swapchain target
IDXGISwapChain* swapChain = nullptr;
ID3D11RenderTargetView* rtv = nullptr;
// Message-pump thread that owns hwnd. Kept open for the window's lifetime so
// DestroyPopoutWindow can join it before freeing this struct (the pump thread
// touches `this` while processing WM_DESTROY).
HANDLE pumpThread = nullptr;
// Swapchain dimensions — content area only (no status bar gap any more;
// the ImGui toolbar is an overlay rendered into the same swapchain).
std::atomic<int> width {800};
std::atomic<int> height {600};
// Texture + UV rect written by RRPOPOUT_SetFrameTexture on the main thread,
// read by Renderer_Present on the render thread.
std::atomic<void*> pendingTexture {nullptr};
std::atomic<float> srcU0 {0.f}, srcV0 {0.f};
std::atomic<float> srcU1 {1.f}, srcV1 {1.f};
std::atomic<bool> resizing {false};
std::atomic<bool> destroyRequested {false};
std::atomic<bool> alive {true};
InputQueue inputQueue;
// -----------------------------------------------------------------------
// ImGui input state
// Written by the Win32 message pump thread; consumed by the render thread.
// -----------------------------------------------------------------------
std::atomic<float> imMouseX {-1.f};
std::atomic<float> imMouseY {-1.f};
std::atomic<bool> imLButton {false};
std::atomic<bool> imRButton {false};
// Wheel accumulator in raw WHEEL_DELTA units (120 per notch).
// fetch_add on message pump; exchange(0) on render thread.
std::atomic<int> imWheelRaw {0};
// Written by render thread after each ImGui frame.
// When true, PollInputEvents suppresses mouse events so they don't reach Unity.
std::atomic<bool> imWantMouse {false};
// When true, Present blits the frame texture only: no map toolbar, compass,
// radio rail, or view presets. Used by Car Cards (and any future non-map window).
std::atomic<bool> imPlainContent {false};
// -----------------------------------------------------------------------
// Status bar label (UTF-8), shown in the ImGui toolbar.
// Written by RRPOPOUT_SetStatusText (main thread), read by render thread.
// -----------------------------------------------------------------------
char imStatusText[256];
std::mutex imStatusMutex;
// -----------------------------------------------------------------------
// Dynamic menu lists (UTF-8 labels), set by RRPOPOUT_SetLocoList /
// RRPOPOUT_SetLocationList from the C# side every few seconds.
// Read by the render thread inside the ImGui settings menu.
// -----------------------------------------------------------------------
struct ImMenuItem { char label[128]; };
std::vector<ImMenuItem> imLocoList;
std::mutex imLocoMutex;
std::vector<ImMenuItem> imLocationList;
std::mutex imLocationMutex;
// -----------------------------------------------------------------------
// Map rotation + ME flag (C# → render thread via exports)
// -----------------------------------------------------------------------
std::atomic<float> imMapRotationDeg {0.f};
std::atomic<float> imMapZoom {500.f};
std::atomic<bool> imMapSyncPlayer {false};
std::atomic<bool> imMapEnhancerInstalled {false};
std::atomic<bool> imMEHasTurntable {false}; // true if ShowTurntableMarkers exists (v1.6.0 or community)
std::atomic<bool> imMECommunity {false}; // true = community build (has crossing/spawn/switch-reset)
std::atomic<bool> imFollowPlayer {false};
std::atomic<bool> imAlwaysOnTop {false};
std::atomic<bool> imPanDisablesFollow {true};
std::atomic<bool> imRightClickRecenter {true};
// -----------------------------------------------------------------------
// Icon culling + EOTD settings (C# → render thread via RRPOPOUT_SetIconCullingState)
// -----------------------------------------------------------------------
std::atomic<bool> imIconCullingEnabled {true};
std::atomic<float> imCullThreshold {0.40f};
std::atomic<bool> imHideMuLocos {true};
std::atomic<float> imLocoBoostedScale {1.5f};
std::atomic<bool> imEotdEnabled {true};
std::atomic<bool> imEotdOnlyWhenCulled {true};
std::atomic<float> imEotdSizeScale {8.0f};
// -----------------------------------------------------------------------
// MapEnhancer settings state (C# → render thread via RRPOPOUT_SetMEState)
// Bit layout of imMEFlags matches MEBoolBit enum in shared_types.h.
// Scale atomics are live-updated during slider drag so the slider doesn't
// snap back between frames, then the final value is pushed as UICmd::SetMEFloat.
// -----------------------------------------------------------------------
std::atomic<uint32_t> imMEFlags {0};
std::atomic<float> imMEFlareScale {0.6f};
std::atomic<float> imMEJunctionSc {0.6f};
std::atomic<float> imMETrackThick {1.25f};
std::atomic<float> imMECrossingSc {0.3f};
// -----------------------------------------------------------------------
// Track name labels (industry track names + viewport UVs, pushed from C# each frame).
// C# projects TrackSpan centroids through the map camera, culls off-screen entries,
// and pushes (name, centroid_uv, anchors). Native runs per-frame collision avoidance
// (AABB push in pixel space) then draws leader lines to each anchor before the text.
// -----------------------------------------------------------------------
struct ImTrackLabel {
char name[64];
float u, v; // centroid UV — starting position for the layout solver
float angle; // track direction in screen space (degrees; 0=right, +ve=CW)
float scale; // font size multiplier (1.0 = track label, 1.5 = industry label)
int anchorStart; // index into imAnchorUs/Vs
int anchorCount; // 1 for single span; >1 for merged same-name cluster
};
std::vector<ImTrackLabel> imTrackLabels;
std::vector<float> imAnchorUs; // flat anchor UV arrays, indexed by anchorStart
std::vector<float> imAnchorVs;
std::mutex imTrackLabelMutex;
std::atomic<bool> imTrackLabelsEnabled {true};
std::atomic<float> imTrackLabelFontSize {14.f};
std::atomic<float> imTrackLabelLineThickness {1.5f};
std::atomic<float> imTrackLabelZoomLimit {3000.f};
std::atomic<bool> imTrackLeaderLinesEnabled {true};
std::atomic<bool> imTrackCollisionEnabled {true};
std::atomic<bool> imTrackParallelEnabled {false};
std::atomic<bool> imTrackMergeEnabled {true};
std::atomic<float> imTrackLabelMergeZoom {150.f};
std::atomic<float> imTrackIndustryZoom {200.f};
std::atomic<bool> imTrackUtilityRepair {true};
std::atomic<bool> imTrackUtilityDiesel {true};
std::atomic<bool> imTrackUtilityLoader {true};
std::atomic<bool> imTrackUtilityInterchange {true};
std::atomic<float> imTrackUtilityZoom {200.f};
std::atomic<float> imTrackAllLabelsZoom {8000.f};
std::atomic<bool> imTrackLabelAvoidTrack {false};
std::atomic<float> imTrackLabelFontSizeMin {8.f};
// Named camera-view presets (C# owns the data; native draws the left rail).
std::vector<ImMenuItem> imPresetList;
std::mutex imPresetMutex;
std::atomic<int> imPresetEditIndex {-1};
std::atomic<bool> imPresetPreviewing {false};
std::atomic<int> imPresetPendingDelete{-1};
char imPresetRenameBuf[128] {};
// AE waypoint pins (gear-menu toggles).
std::atomic<bool> imWaypointsEnabled {true};
std::atomic<bool> imWaypointsSelectedOnly {false};
// Radio-control rail (top-right). C# owns pin ids; native draws the list.
std::vector<ImMenuItem> imRadioList;
std::vector<uint32_t> imRadioColors;
std::mutex imRadioMutex;
std::atomic<bool> imRadioOn {false};
std::atomic<int> imRadioSelected {-1};
std::atomic<int> imRadioTool {0};
std::atomic<bool> imRadioWq {false};
std::atomic<uint64_t> imRadioAeBits {0};
std::atomic<bool> imRadioForward {true};
std::atomic<float> imRadioSpeed {15.f};
std::atomic<int> imRadioEditIndex {-1};
char imRadioRenameBuf[128] {};
// Waypoint-mode ghost arrow (Unity viewport UV, v=0 at bottom) + near-cursor popup.
std::atomic<bool> imRadioGhostOn {false};
std::atomic<float> imRadioGhostU {0.f};
std::atomic<float> imRadioGhostV {0.f};
std::atomic<float> imRadioGhostAngle {0.f}; // screen deg, 0=right, +CW, y-down
std::atomic<uint32_t> imRadioGhostColor {0xFFFFFFFFu};
std::atomic<int> imRadioWpStage {0}; // 0 off, 1 choose order, 2 enter count
std::atomic<float> imRadioWpU {0.f};
std::atomic<float> imRadioWpV {0.f};
std::atomic<int> imRadioWpFlags {0}; // bit0 = has couple target
std::atomic<int> imRadioWpCount {1};
// Keyboard for ImGui InputText (preset rename). Pump thread writes, render thread reads.
std::mutex imKeyMutex;
char imCharsUtf8[512] {};
std::atomic<uint32_t> imKeyDown {0};
std::atomic<uint32_t> imKeyMods {0};
uint32_t imPrevKeyDown = 0;
uint32_t imPrevKeyMods = 0;
// Loaded geometry from the save file — consumed by MessagePumpThread before CreateWindowExW.
std::atomic<int> lastWinX {-1}, lastWinY {-1};
std::atomic<int> lastWinW {900}, lastWinH {700};
PopoutWindow() {
// Status starts empty (filled live with zoom/coords). Avoids a default label
// that would be redundant with the window title bar and would render any
// non-ASCII glyph as '?' in ImGui's default font.
imStatusText[0] = '\0';
}
};