using System; namespace AcDream.UI.Abstractions.Input; /// /// Phase K.2 — state machine for MMB-hold "instant mouse-look" mode /// (retail's CameraInstantMouseLook). While active, mouse-X /// delta drives the character's heading AND the chase camera yaw /// together (combined drive — the camera "instantly" follows the /// character because mouse-X moves the character, and the chase /// camera always tracks the character). Mouse-Y is left to the /// caller — typically pitches the chase camera only. /// /// /// The class owns three transitions: /// /// — MMB pressed AND ImGui isn't hovering a /// panel; activate, capture initial cursor position for restore. /// — MMB released; deactivate, signal /// cursor restore. /// — ImGui took mouse /// focus while we were active (e.g. a panel pop-up); deactivate AS IF /// the user released the button so the cursor is restored. /// /// /// /// /// is the per-frame mouse-move hook: when /// active, scales by sensitivity and feeds the /// caller-supplied yaw mutator. The mutator is the only side-channel — /// the class doesn't know about PlayerMovementController or /// ChaseCamera. /// /// public sealed class MouseLookState { private readonly Action _applyYawDelta; /// True while MMB is held AND ImGui isn't capturing the /// mouse. Mouse-X deltas drive yaw only when this is true. public bool Active { get; private set; } /// Cursor X at the moment activated. /// Restore on release. public float CapturedCursorX { get; private set; } /// Cursor Y at the moment activated. public float CapturedCursorY { get; private set; } /// /// Per-radian yaw multiplier applied to mouse-X delta. The same /// chase-camera sensitivity factor used elsewhere in GameWindow is /// folded in by the caller; this class only owns the constant /// scale that converts pixels to radians. Default 0.004 matches /// the K.1b RMB-orbit factor. /// public float SensitivityRadiansPerPixel { get; set; } = 0.004f; public MouseLookState(Action applyYawDelta) { _applyYawDelta = applyYawDelta ?? throw new ArgumentNullException(nameof(applyYawDelta)); } /// /// MMB press transition. Activates only when ImGui isn't capturing /// the mouse — the dispatcher should already gate this, but the /// guard adds defense in depth in case a binding fires through. /// /// Current cursor X (captured for restore on release). /// Current cursor Y. /// Mirror of /// ImGui.GetIO().WantCaptureMouse. When true, the press is /// ignored so a hover-over-panel MMB doesn't grab the cursor. public void Press(float cursorX, float cursorY, bool wantCaptureMouse) { if (wantCaptureMouse) return; if (Active) return; Active = true; CapturedCursorX = cursorX; CapturedCursorY = cursorY; } /// MMB release transition. Always deactivates if active. public void Release() { if (!Active) return; Active = false; } /// /// Reactive deactivation when ImGui takes mouse focus mid-hold. /// E.g. a tooltip pops open over the cursor while MMB is held — /// we yield the cursor to the panel instead of staying captured. /// public void OnWantCaptureMouseChanged(bool wantCaptureMouse) { if (Active && wantCaptureMouse) Active = false; } /// /// Apply a per-frame mouse-X delta. When active, scales by /// times the /// caller-supplied (typically /// the chase-camera sens) and feeds it to the yaw mutator. Sign /// matches retail: dragging the mouse RIGHT yaws the character to /// the right (positive yaw delta). /// /// Pixels of horizontal mouse motion since the /// last frame. /// Multiplier (e.g. chase-camera /// sensitivity) applied on top of the radians-per-pixel scale. public void ApplyDelta(float dx, float extraSensitivity) { if (!Active) return; // Sign: dragging the mouse RIGHT (dx > 0) should yaw the // character to the right. With the acdream Yaw convention // (where Yaw 0 = +X, increasing to +Y), positive yaw is // counter-clockwise viewed top-down — so dragging right means // yaw goes DOWN (more clockwise). The dispatcher convention // for chase YawOffset is `YawOffset -= dx * factor`; we keep // the same sign here so character + camera rotate identically. _applyYawDelta(-dx * SensitivityRadiansPerPixel * extraSensitivity); } }