railroader-setons-special-s.../src/Modules/Popout/NativeInterop.cs
seton dd1af2ec30 v0.2.3 - MapEnhancer settings panel, opacity controls, right-click recenter
Extends the existing MapEnhancer gear menu integration with a full settings
panel covering the options added by the community fork: marker visibility
toggles, scale sliders for flares, junctions, track lines, and crossings,
and bulk switch reset buttons.

Also adds pan-cancels-follow and right-click to recenter on player toggles,
three independent opacity controls (Window, Map Elements, Map Background),
a per-element hover minimum of 50% for the window chrome so controls stay
reachable at low opacity, and fixes to make the compass and toolbar correctly
respond to their respective sliders. Both surfaces (in-game overlay and OS
popout) maintain parity on all new settings.
2026-06-19 08:32:05 -04:00

236 lines
13 KiB
C#

using System;
using System.Runtime.InteropServices;
namespace S3.Modules.Popout {
// Must match InputEvent in native/include/shared_types.h exactly.
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct InputEvent {
public int type; // InputEventType (0=Move,1=LDown,2=LUp,3=RDown,4=RUp,5=Wheel)
public float x; // normalized [0,1] left=0
public float y; // normalized [0,1] top=0
public float delta; // wheel delta; positive = scroll up
}
public enum InputEventType {
MouseMove = 0,
LButtonDown = 1,
LButtonUp = 2,
RButtonDown = 3,
RButtonUp = 4,
MouseWheel = 5,
UICommand = 6, // toolbar button; x = UICmd value cast to float
}
public enum UICmd {
Recenter = 1,
FollowMode = 2, // toggle MapEnhancer follow on/off
FollowPlayerCam = 3, // follow Camera.main continuously
FollowSelectedLoco = 4, // follow TrainController.Shared.SelectedCar
FollowLoco = 5, // follow specific loco; InputEvent.y = 0-based index
JumpToLocation = 6, // jump to named location; InputEvent.y = 0-based index
SetRotation = 7, // set map rotation; InputEvent.y = degrees [0,360)
RotateReset = 8, // reset map rotation to 0
RotateSyncPlayer = 9, // toggle sync-rotation-to-player-camera mode
ToggleFollowPlayer = 10, // toggle continuous follow of player world position
PopOut = 11, // (in-game only) close the overlay and open the OS popout
SetTheme = 12, // (in-game only) apply theme preset; InputEvent.y = preset index
SetAlpha = 13, // (in-game only) set chrome alpha; InputEvent.y = [0.1, 1.0]
Close = 14, // (in-game only) user clicked [X] — hide the overlay
SetMapAlpha = 15, // (in-game only) set map image alpha; InputEvent.y = [0.0, 1.0]
// MapEnhancer settings
SetMEBool = 16, // toggle ME bool setting; y = MEBoolBit index, delta = 0|1
SetMEFloat = 17, // set ME float setting; y = MEFloatIdx index, delta = value
MEResetSwitchesNormal = 18, // bulk reset all switches to Normal
MEResetSwitchesThrown = 19, // bulk reset all switches to Thrown
TogglePanDisablesFollow = 20, // toggle whether panning cancels follow mode
ToggleRightClickRecenter = 21, // toggle whether right-clicking recenters on player
SetMapBgAlpha = 22, // set map camera clear-colour opacity; y = [0.0, 1.0]
}
// Must match MapThemeData in native/include/shared_types.h exactly (36 floats = 144 bytes).
[Serializable]
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct MapThemeData {
public float wBgR, wBgG, wBgB, wBgA; // window / toolbar background
public float accR, accG, accB, accA; // accent (TitleBgActive, buttons)
public float txtR, txtG, txtB, txtA; // status text
public float popR, popG, popB, popA; // settings popup background
public float mapR, mapG, mapB, mapA; // map image tint (1,1,1,1 = no tint)
public float cmpR, cmpG, cmpB, cmpA; // compass background disk
public float nNR, nNG, nNB, nNA; // compass needle N half
public float nSR, nSG, nSB, nSA; // compass needle S half
public float mapBgR, mapBgG, mapBgB, mapBgA; // map camera clear colour
}
internal static class Native {
// S3Native.dll lives in the mod folder (Mods/S3) and is loaded by full path
// via NativeLoader before any of these are called; once loaded, [DllImport]
// binds to it by name. (Pure-UMM install — no game-root copy, no winhttp proxy.)
// Named S3Native (not RRPopout) so it can never collide with a leftover copy
// of the old standalone PopOut's RRPopout.dll still injected in someone's game.
private const string Dll = "S3Native";
// Create a floating window. Returns a handle (> 0) or 0 on failure.
// Native loads saved position/size from its state file; width/height are the first-run defaults.
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern int RRPOPOUT_CreateWindow(string title, int width, int height);
// Set the source texture and the UV sub-rect to blit next frame.
// Call this on the main thread immediately before IssuePluginEvent.
// u0,v0 = top-left UV in D3D convention (V=0 at top); u1,v1 = bottom-right.
// Pass (0, 0, 1, 1) for full texture.
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern void RRPOPOUT_SetFrameTexture(
int windowHandle, IntPtr texturePtr,
float u0, float v0, float u1, float v1);
// Returns the UnityRenderingEvent callback pointer for GL.IssuePluginEvent.
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr RRPOPOUT_GetRenderEventFunc();
// Fills outWidth / outHeight with the current client area size.
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern void RRPOPOUT_GetWindowSize(int windowHandle, out int outWidth, out int outHeight);
// Drains queued input events from the OS window into outEvents.
// Returns the number of events written (≤ maxEvents).
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern int RRPOPOUT_PollInputEvents(
int windowHandle,
[Out] InputEvent[] outEvents,
int maxEvents);
// Close and free the window.
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern void RRPOPOUT_DestroyWindow(int windowHandle);
// Update the status bar text shown at the bottom of the popout.
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern void RRPOPOUT_SetStatusText(int windowHandle, string text);
// Set the locomotive list shown in the Follow submenu.
// names: loco display names joined by '\n'.
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern void RRPOPOUT_SetLocoList(int windowHandle, string names);
// Set the location list shown in the Jump to location submenu.
// names: location names joined by '\n'.
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern void RRPOPOUT_SetLocationList(int windowHandle, string names);
// Tell the native UI whether MapEnhancer is installed (controls menu layout).
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern void RRPOPOUT_SetMapEnhancerInstalled(int windowHandle, bool installed);
// Update the compass display to reflect the current map rotation (degrees).
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern void RRPOPOUT_SetMapRotation(int windowHandle, float degrees);
// Highlight (or un-highlight) the sync button in the toolbar.
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern void RRPOPOUT_SetMapSyncPlayer(int windowHandle, bool active);
// Highlight (or un-highlight) the follow-player toolbar button.
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern void RRPOPOUT_SetFollowPlayer(int windowHandle, bool active);
// Push current map zoom so native can persist it on window close.
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern void RRPOPOUT_SetMapZoom(int windowHandle, float zoom);
// Read back the C#-side state that native loaded from its save file.
// followPlayer / syncRotation return 1 (true) or 0 (false).
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern void RRPOPOUT_GetPersistedState(int windowHandle,
out int followPlayer, out int syncRotation,
out float mapRotation, out float mapZoom);
// -------------------------------------------------------------------
// In-game overlay. Shares the native engine's shared ImGui context
// and D3D11 device; renders into Unity's bound backbuffer after frame.
// -------------------------------------------------------------------
// Event id to pass to GL.IssuePluginEvent to drive the overlay render.
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern int RRPOPOUT_GetOverlayEventId();
// One-time: hand native a texture so it can acquire Unity's D3D device.
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern void RRPOPOUT_SetOverlayDeviceTexture(IntPtr texturePtr);
// Per-frame input snapshot (top-left origin, pixels; wheel in WHEEL_DELTA units).
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern void RRPOPOUT_SetOverlayInput(
float displayW, float displayH,
float mouseX, float mouseY,
int lButton, int rButton, int wheel);
// Show or hide the overlay.
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern void RRPOPOUT_SetOverlayVisible(int visible);
// Map render texture to display inside the in-game overlay window, plus the
// UV sub-rect (V flipped for a Unity RT: v0=1, v1=0). IntPtr.Zero clears it.
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern void RRPOPOUT_SetOverlayMapTexture(
IntPtr texturePtr, float u0, float v0, float u1, float v1);
// 1 when ImGui wants the mouse this frame (hovering a widget).
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern int RRPOPOUT_OverlayWantsMouse();
// Drains queued in-game map input (drag/zoom over the map image). Events are
// in normalized [0,1] image space (top-left origin); forward to the map camera.
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern int RRPOPOUT_PollOverlayInput(
[Out] InputEvent[] outEvents, int maxEvents);
// Reads the map image region size (px) so C# can set camera.aspect to match,
// filling the window with no letterbox bars.
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern void RRPOPOUT_GetOverlayMapView(out float outW, out float outH);
// Handle of the in-game overlay's shared UI-state holder. Push state to it
// (status/loco/location/rotation/follow) and poll it for toolbar commands
// with the same handle-based functions the popout uses.
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern int RRPOPOUT_GetOverlayWindowHandle();
// Apply a theme to the shared ImGui context. Affects both the in-game overlay
// and the popout (they share one context). presetIndex is stored for the gear
// menu checkmarks. Thread-safe: stored on main thread, applied on render thread.
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern void RRPOPOUT_SetTheme(ref MapThemeData data, int presetIndex);
// Set the in-game overlay's chrome alpha [0.1, 1.0]. Thread-safe via atomic.
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern void RRPOPOUT_SetOverlayAlpha(float alpha);
// Set the map image alpha [0.0, 1.0]. Independent of chrome alpha.
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern void RRPOPOUT_SetOverlayMapAlpha(float alpha);
// Tell native whether panning should cancel follow mode (for the gear menu checkmark).
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern void RRPOPOUT_SetPanDisablesFollow(int windowHandle, bool active);
// Tell native whether right-clicking the map recenters on the player (gear menu checkmark).
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern void RRPOPOUT_SetRightClickRecenter(int windowHandle, bool active);
// Seed the map camera clear-colour opacity slider (shared global, overlay + popout).
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern void RRPOPOUT_SetOverlayMapBgAlpha(float alpha);
// Push MapEnhancer settings state to a window's render-thread atomics.
// flags: bit-packed booleans (see MEBoolBit in shared_types.h).
// Scale values seed slider positions; sliders update them live during drag
// and push UICmd::SetMEFloat only on release (IsItemDeactivatedAfterEdit).
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern void RRPOPOUT_SetMEState(
int windowHandle, uint flags,
float flareScale, float junctionScale,
float trackThickness, float crossingScale);
}
}