|
1 | 1 | // Licensed to the .NET Foundation under one or more agreements. |
2 | 2 | // The .NET Foundation licenses this file to you under the MIT license. |
3 | 3 |
|
| 4 | +using System.Numerics; |
| 5 | +using Silk.NET.SDL; |
| 6 | + |
4 | 7 | namespace Silk.NET.Input.SDL3; |
5 | 8 |
|
6 | | -internal class SdlGamepad : IGamepad |
| 9 | +internal class SdlGamepad : SdlDevice, IGamepad, IDisposable |
7 | 10 | { |
8 | | - public bool Equals(IInputDevice? other) => throw new NotImplementedException(); |
| 11 | + private readonly GamepadHandle _gamepad; |
| 12 | + |
| 13 | + private static JoystickButton? GetSilkButton(GamepadButton btn) => |
| 14 | + btn switch |
| 15 | + { |
| 16 | + GamepadButton.South => JoystickButton.ButtonDown, |
| 17 | + GamepadButton.East => JoystickButton.ButtonRight, |
| 18 | + GamepadButton.West => JoystickButton.ButtonLeft, |
| 19 | + GamepadButton.North => JoystickButton.ButtonUp, |
| 20 | + GamepadButton.Back => JoystickButton.Back, |
| 21 | + GamepadButton.Guide => JoystickButton.Home, |
| 22 | + GamepadButton.Start => JoystickButton.Start, |
| 23 | + GamepadButton.LeftStick => JoystickButton.LeftStick, |
| 24 | + GamepadButton.RightStick => JoystickButton.RightStick, |
| 25 | + GamepadButton.LeftShoulder => JoystickButton.LeftBumper, |
| 26 | + GamepadButton.RightShoulder => JoystickButton.RightBumper, |
| 27 | + GamepadButton.DpadUp => JoystickButton.DPadUp, |
| 28 | + GamepadButton.DpadDown => JoystickButton.DPadDown, |
| 29 | + GamepadButton.DpadLeft => JoystickButton.DPadLeft, |
| 30 | + GamepadButton.DpadRight => JoystickButton.DPadRight, |
| 31 | + // TODO not exposed today |
| 32 | + _ => null, |
| 33 | + }; |
| 34 | + |
| 35 | + public SdlGamepad(SdlInputBackend backend, uint joystickId) |
| 36 | + : base(backend) |
| 37 | + { |
| 38 | + _gamepad = backend.Sdl.OpenGamepad(joystickId); |
| 39 | + if (_gamepad == nullptr) |
| 40 | + { |
| 41 | + backend.Sdl.ThrowError(); |
| 42 | + } |
| 43 | + |
| 44 | + var buttons = InputMarshal.CreateList<Button<JoystickButton>>(); |
| 45 | + for (var i = 0; i < (int)GamepadButton.Count; i++) |
| 46 | + { |
| 47 | + if (GetSilkButton((GamepadButton)i) is not { } btn) |
| 48 | + { |
| 49 | + continue; |
| 50 | + } |
| 51 | + |
| 52 | + var isDown = backend.Sdl.GetGamepadButton(_gamepad, (GamepadButton)i); |
| 53 | + InputMarshal.SetButtonState( |
| 54 | + buttons, |
| 55 | + new Button<JoystickButton>(btn, isDown, isDown ? 1 : 0), |
| 56 | + true |
| 57 | + ); |
| 58 | + } |
| 59 | + |
| 60 | + // For thumbsticks, the state is a value ranging from -32768 (up/left) to 32767 (down/right). |
| 61 | + // Triggers range from 0 when released to 32767 when fully pressed, and never return a negative value. Note that |
| 62 | + // this differs from the value reported by the lower-level SDL_GetJoystickAxis(), which normally uses the full |
| 63 | + // range. |
| 64 | + var triggers = new DualReadOnlyList<float>( |
| 65 | + (float)backend.Sdl.GetGamepadAxis(_gamepad, GamepadAxis.LeftTrigger) / short.MaxValue, |
| 66 | + (float)backend.Sdl.GetGamepadAxis(_gamepad, GamepadAxis.RightTrigger) / short.MaxValue |
| 67 | + ); |
| 68 | + var thumbsticks = new DualReadOnlyList<Vector2>( |
| 69 | + new Vector2( |
| 70 | + (float)backend.Sdl.GetGamepadAxis(_gamepad, GamepadAxis.Leftx) / short.MaxValue, |
| 71 | + (float)backend.Sdl.GetGamepadAxis(_gamepad, GamepadAxis.Lefty) / short.MaxValue |
| 72 | + ), |
| 73 | + new Vector2( |
| 74 | + (float)backend.Sdl.GetGamepadAxis(_gamepad, GamepadAxis.Rightx) / short.MaxValue, |
| 75 | + (float)backend.Sdl.GetGamepadAxis(_gamepad, GamepadAxis.Righty) / short.MaxValue |
| 76 | + ) |
| 77 | + ); |
| 78 | + State = new GamepadState(buttons.List.AsButtonList(), thumbsticks, triggers); |
| 79 | + } |
| 80 | + |
| 81 | + // TODO this is not spec compliant, we need to use a physical device ID |
| 82 | + public override unsafe nint Id => (nint)_gamepad.Handle; |
| 83 | + |
| 84 | + public override string Name => Backend.Sdl.GetGamepadName(_gamepad).ReadToString(); |
| 85 | + |
| 86 | + public GamepadState State { get; } |
| 87 | + |
| 88 | + // TODO this entire API needs to be redesigned as right now this is literally only ever going to be useful if it's |
| 89 | + // just left or right. The original intention was that this would be useful for things like 3D haptics, but what did |
| 90 | + // I know. The SDL people seem to have done a good job with their haptic API, let's see what we can do with it. |
| 91 | + // For now, this has the same implementation as it always has. |
| 92 | + public IReadOnlyList<IMotor> VibrationMotors => |
| 93 | + _motors ??= [new SdlMotor(this, 0), new SdlMotor(this, 1)]; |
| 94 | + |
| 95 | + private IMotor[]? _motors; |
| 96 | + private ushort[]? _motorFrequencies; |
| 97 | + |
| 98 | + internal ushort GetRumble(int motor) => (_motorFrequencies ??= [0, 0])[motor]; |
9 | 99 |
|
10 | | - public IntPtr Id => throw new NotImplementedException(); |
| 100 | + internal void SetRumble(int motor, ushort value) |
| 101 | + { |
| 102 | + (_motorFrequencies ??= [0, 0])[motor] = value; |
| 103 | + if ( |
| 104 | + !Backend.Sdl.RumbleGamepad( |
| 105 | + _gamepad, |
| 106 | + _motorFrequencies[0], |
| 107 | + _motorFrequencies[1], |
| 108 | + uint.MaxValue |
| 109 | + ) |
| 110 | + ) |
| 111 | + { |
| 112 | + Backend.Sdl.ThrowError(); |
| 113 | + } |
| 114 | + } |
11 | 115 |
|
12 | | - public string Name => throw new NotImplementedException(); |
| 116 | + private void ReleaseUnmanagedResources() => Backend.Sdl.CloseGamepad(_gamepad); |
13 | 117 |
|
14 | | - public GamepadState State => throw new NotImplementedException(); |
| 118 | + public void Dispose() |
| 119 | + { |
| 120 | + ReleaseUnmanagedResources(); |
| 121 | + GC.SuppressFinalize(this); |
| 122 | + } |
15 | 123 |
|
16 | | - public IReadOnlyList<IMotor> VibrationMotors => throw new NotImplementedException(); |
| 124 | + ~SdlGamepad() => ReleaseUnmanagedResources(); |
17 | 125 | } |
0 commit comments