using NativeEngine; namespace Sandbox.Engine; internal static partial class InputRouter { static RealTimeSince timeSinceWindowActive; internal static void OnMouseButton( ButtonCode button, bool down, int ikeymods ) { SetButtonState( button, down ); var mouse = Contexts.FirstOrDefault( x => x.MouseState != InputContext.InputState.Ignore ); // if this was likely the click that made the window active - and we're not in UI mode // then ignore it.. because we don't want people shooting guns every time they re-activate // the window if ( down && timeSinceWindowActive < 0.1f ) { if ( mouse is null || mouse.MouseState != InputContext.InputState.UI ) return; } if ( mouse is not null ) { mouse.IN_Button( down, button, false, EngineToModifier( ikeymods ) ); } // // When a button is released, we send it to each context. These contexts may have // the button as pressed. The release should be sent to them so they can clear it. // if ( !down ) { foreach ( var context in Contexts ) { if ( context == mouse ) continue; context.IN_ButtonReleased( button, EngineToModifier( ikeymods ) ); } } } internal static void OnWindowActive( bool active ) { if ( active ) { timeSinceWindowActive = 0; } else { // // Reset state and do button release events // PressedButtons.Clear(); foreach ( var context in Contexts ) { context.ReleaseAllButtons(); } } } /// /// Cursor is hidden and restricted to window (game mode) but the mouse has been moved /// internal static void OnMouseMotion( float dx, float dy ) { var delta = new Vector2( dx, dy ); MouseCursorDelta += delta; MouseCursorPosition += delta; // game or mouse capture var mouse = Contexts.FirstOrDefault( x => x.MouseState != InputContext.InputState.Ignore ); if ( mouse is not null ) { mouse.In_MousePosition( MouseCursorPosition, delta ); return; } } /// /// Cursor is visible and mouse is being moved /// internal static void OnMousePositionChange( float x, float y, float dx, float dy ) { var delta = new Vector2( 0, 0 ); // if we're not in relative mode - take the delta from this if ( !NativeEngine.InputSystem.GetRelativeMouseMode() ) { delta = new Vector2( dx, dy ); MouseCursorDelta += delta; } MouseCursorPosition = new Vector2( x, y ); // if this is set, we're in capture mode - so just update the position // which will update the position of the cursor when we come out of it if ( mouseCapturePosition is not null ) { mouseCapturePosition = MouseCursorPosition; return; } var mouse = Contexts.FirstOrDefault( x => x.MouseState != InputContext.InputState.Ignore ); if ( mouse is not null ) { mouse.In_MousePosition( MouseCursorPosition, delta ); } } internal static void OnGameControllerButton( int deviceId, GameControllerCode button, bool down ) { if ( button == GameControllerCode.Start ) { OnEscapePressed( down ); return; } OnGamepadCode( deviceId, button.ToGamepadCode(), down ); } /// /// The escape, or start button has been pressed /// private static void OnEscapePressed( bool down ) { EscapeIsDown = down; if ( down ) { EscapeWasPressed = true; TimeSinceEscapePressed = 0; // Any focused UI? Let that swallow the escape button. This is for things like escaping from text areas var escapeTarget = Contexts.FirstOrDefault( x => x.KeyboardState != InputContext.InputState.Ignore && x.KeyboardFocusPanel is not null ); if ( escapeTarget is not null && escapeTarget.In_Escape() ) { return; } // Let the game input get first dibs if ( IGameInstance.Current is not null && IGameInstanceDll.Current.InputContext.In_Escape() ) { return; } } } private static void OnGamepadCode( int deviceId, GamepadCode code, bool down ) { var controller = Controller.All.FirstOrDefault( x => x.DeviceId == deviceId ); Log.Trace( $"OnGameControllerButton {controller} - {code}, {down}" ); Sandbox.Input.UsingController = true; SetButtonState( code, down ); foreach ( var action in Sandbox.Input.InputActions.Where( x => x.GamepadCode != GamepadCode.None && x.GamepadCode == code ) ) { var i = Sandbox.Input.GetActionIndex( action ); foreach ( var e in Sandbox.Input.Contexts ) { if ( down ) { e.AccumActionsPressed |= 1UL << i; } else { e.AccumActionsReleased |= 1UL << i; } } } } internal static void OnGameControllerAxis( int deviceId, GameControllerAxis axis, int value ) { var controller = Controller.All.FirstOrDefault( x => x.DeviceId == deviceId ); Log.Trace( $"OnGameControllerButton {controller} - {axis}, {value}" ); if ( controller is null ) return; controller.SetAxis( axis, value ); //// We're going to check our triggers and convert them into a virtual button, for ease of use //// (and backwards compatibility with SteamInput) //// We don't care about non triggers if ( axis < GameControllerAxis.TriggerLeft ) return; const float triggerDeadzone = 0.75f; // I hate this but okay GamepadCode code = axis switch { GameControllerAxis.TriggerLeft => GamepadCode.LeftTrigger, GameControllerAxis.TriggerRight => GamepadCode.RightTrigger, _ => GamepadCode.None, }; OnGamepadCode( deviceId, code, value >= triggerDeadzone ); } internal static void OnGameControllerConnected( int joystickId, int deviceId ) { var controller = new Controller( joystickId, deviceId ); Log.Info( $"New {controller} controller detected" ); Controller.All.Add( controller ); } internal static void OnGameControllerDisconnected( int joystickId ) { var controller = Controller.All.FirstOrDefault( x => x.SDLHandle == joystickId ); if ( controller is not null ) { Log.Info( $"{controller} controller removed" ); Controller.All.Remove( controller ); } else { Log.Warning( $"Couldn't find Controller instance with {joystickId}" ); } } internal static void OnKey( ButtonCode button, bool down, bool repeat, int ikeymods, int vkcode ) { if ( !repeat ) { SetButtonState( button, down ); } if ( button == ButtonCode.KEY_ESCAPE ) { if ( repeat ) return; OnEscapePressed( down ); return; } // // Function keys // if ( button >= ButtonCode.KEY_F1 && button <= ButtonCode.KEY_F12 ) { if ( !down || repeat ) return; IToolsDll.Current?.OnFunctionKey( button, EngineToModifier( ikeymods ) ); var bind = g_pInputService.GetBinding( button ); if ( string.IsNullOrEmpty( bind ) ) return; ConVarSystem.Run( bind ); return; } // // Console // if ( button == ButtonCode.KEY_BACKQUOTE || button == ButtonCode.KEY_TILDE ) { if ( !down || repeat ) return; if ( Engine.IToolsDll.Current?.ConsoleFocus() ?? false ) return; ConVarSystem.Run( "con_toggle" ); return; } var keyboard = Contexts.FirstOrDefault( x => x.KeyboardState != InputContext.InputState.Ignore ); if ( keyboard is not null ) { keyboard.IN_Button( down, button, repeat, EngineToModifier( ikeymods ) ); } // // When a button is released, we send it to each context. These contexts may have // the button as pressed. The release should be sent to them so they can clear it. // if ( !down ) { foreach ( var context in Contexts ) { if ( context == keyboard ) continue; context.IN_ButtonReleased( button, EngineToModifier( ikeymods ) ); } } } internal static void OnText( uint key ) { var keyboard = Contexts.FirstOrDefault( x => x.KeyboardState == InputContext.InputState.UI ); if ( keyboard is not null ) { keyboard.IN_Text( (char)key ); } } internal static void OnMouseWheel( int x, int y, int ikeymods ) { var value = new Vector2( x, y ); var mouse = Contexts.FirstOrDefault( x => x.MouseState != InputContext.InputState.Ignore ); if ( mouse is not null ) { if ( y < 0 ) { mouse.IN_Button( true, ButtonCode.MouseWheelDown, false, default ); mouse.IN_Button( false, ButtonCode.MouseWheelDown, false, default ); } else { mouse.IN_Button( true, ButtonCode.MouseWheelUp, false, default ); mouse.IN_Button( false, ButtonCode.MouseWheelUp, false, default ); } mouse.IN_MouseWheel( value, EngineToModifier( ikeymods ) ); } } internal static void OnImeStart() { var keyboard = Contexts.FirstOrDefault( x => x.KeyboardState != InputContext.InputState.Ignore ); if ( keyboard is not null ) { keyboard.IN_ImeStart(); } } internal static void OnImeComposition( string text, bool final ) { var keyboard = Contexts.FirstOrDefault( x => x.KeyboardState != InputContext.InputState.Ignore ); if ( keyboard is not null ) { keyboard.IN_ImeComposition( text, final ); } } internal static void OnImeEnd() { var keyboard = Contexts.FirstOrDefault( x => x.KeyboardState != InputContext.InputState.Ignore ); if ( keyboard is not null ) { keyboard.IN_ImeEnd(); } } /// /// Convert engine (IE_ShiftPressed etc) to our KeyboardModifiers enum /// static KeyboardModifiers EngineToModifier( int engine ) { KeyboardModifiers m = KeyboardModifiers.None; if ( (engine & 1) == 1 ) m |= KeyboardModifiers.Shift; if ( (engine & 2) == 2 ) m |= KeyboardModifiers.Ctrl; if ( (engine & 4) == 4 ) m |= KeyboardModifiers.Alt; //if ( (m_nData2 & 8) == 8 ) m |= KeyboardModifiers.Windows; //if ( (m_nData2 & 16) == 8 ) m |= KeyboardModifiers.Finger; return m; } }