33
44using System . Numerics ;
55using System . Runtime . CompilerServices ;
6- using Silk . NET . Maths ;
76using Silk . NET . SDL ;
87
98namespace Silk . NET . Input . SDL3 . Devices . Pointers ;
@@ -15,14 +14,17 @@ internal sealed class SdlMouse : SdlPointerDevice, IMouse, ISdlDevice<SdlMouse>
1514
1615 private readonly MouseState _state ;
1716
17+ // the mouse is always considered "down" - there is no up/down state for the mouse pointer itself - only its buttons.
18+ private const bool DownState = true ;
19+
1820 private SdlMouse ( ulong sdlDeviceId , nint uniqueId , SdlInputBackend backend , IPointerTarget unboundedPointerTarget ,
1921 ICursorConfiguration cursor )
2022 : base ( backend , uniqueId , sdlDeviceId , unboundedPointerTarget )
2123 {
2224 _state = new MouseState ( Buttons , Points , Vector2 . Zero ) ;
2325 Cursor = cursor ;
2426 float x = 0 , y = 0 ;
25- var mouseInputFlags = GetButtonMaskSdl ( ref x , ref y ) ;
27+ var mouseInputFlags = GetMouseState ( ref x , ref y ) ;
2628 ApplyMouseButtonState ( mouseInputFlags ) ;
2729
2830 var window = NativeBackend . GetMouseFocus ( ) ;
@@ -40,28 +42,30 @@ private SdlMouse(ulong sdlDeviceId, nint uniqueId, SdlInputBackend backend, IPoi
4042 }
4143 }
4244
43- var pressure = _state . Buttons [ PointerButton . Primary ] . Pressure ;
44- SetTargetPoint ( windowId , new Vector3 ( x , y , 0 ) , pressure ) ;
45+ // var pressure = _state.Buttons[PointerButton.Primary].Pressure;
46+ AddOrUpdatePoint ( 0 , windowId , new Vector3 ( x , y , 0 ) , null , DownState , null , true ) ;
4547 // var point = _unboundedPointerTarget.GetPoint(this, 0);
4648 }
4749
50+
4851 private void ApplyMouseButtonState ( SdlMouseInputFlags mouseState )
4952 {
5053 foreach ( var pointerButtonName in EnumInfo < PointerButton > . UniqueValues )
5154 {
52- ref var button = ref GetButtonRef ( pointerButtonName ) ;
53- var isDown = mouseState . Has ( pointerButtonName ) ;
54- button = button with { IsDown = isDown , Pressure = isDown ? 1 : 0 } ;
55+ if ( mouseState . Has ( pointerButtonName ) )
56+ {
57+ AddButtonEvent ( pointerButtonName , 0 , true ) ;
58+ }
5559 }
5660 }
5761
5862 public override void Initialize ( )
5963 {
60-
6164 }
6265
63- private unsafe SdlMouseInputFlags GetButtonMaskSdl ( ref float x , ref float y ) =>
64- ( SdlMouseInputFlags ) NativeBackend . GetMouseState ( ( float * ) Unsafe . AsPointer ( ref x ) , ( float * ) Unsafe . AsPointer ( ref y ) ) ;
66+ private unsafe SdlMouseInputFlags GetMouseState ( ref float x , ref float y ) =>
67+ ( SdlMouseInputFlags ) NativeBackend . GetMouseState ( ( float * ) Unsafe . AsPointer ( ref x ) ,
68+ ( float * ) Unsafe . AsPointer ( ref y ) ) ;
6569
6670 public static SdlMouse CreateDevice ( ulong sdlDeviceId , SdlInputBackend backend )
6771 {
@@ -113,66 +117,81 @@ private bool IsMouseRelative
113117 }
114118
115119 protected override bool OnePointOnly => true ;
116-
120+ private bool _hintsAsEvents = false ;
121+
122+
123+ /// <summary>
124+ /// <inheritdoc/>
125+ /// </summary>
126+ /// <param name="position">The window-relative position of the mouse</param>
127+ /// <returns>True if success, but see the below remarks</returns>
128+ /// <remarks>
129+ /// todo: this is the most straightforward way to do window-relative movement,
130+ /// but we don't actually get any information about whether or not
131+ /// it succeeds. The SDL documentation seems to suggest that it "just works" as opposed to their global mouse warp
132+ /// method, which provides a success result. instead of translating this "global" position to a window-specific one,
133+ /// we're gonna use SDL's implementation of window-relative movement to keep things simple.<br/><br/>
134+ /// As a result, we always return true, though the documentation suggests that it does not work specifically for
135+ /// Microsoft Remote Desktop. not sure how to detect that at the moment.<br/><br/>
136+ /// Another consequence of doing it this way is that we need to wait for SDL to push these events through the event
137+ /// queue, so while this may return 'true', this mouse object will not necessarily be in the correct position until
138+ /// the next time pumped events are processed.<br/><br/>
139+ /// We could just create a mouse motion event and push it through our internal event queues, bypassing SDL's event
140+ /// queue, but that could result in a situation where we set our internal state to represent the
141+ /// movement, even though SDL did not meaningfully succeed.<br/><br/>
142+ /// The best way around this would probably be to try global mouse warping with translated coordinates,
143+ /// and if that fails, fall back to window-relative movement.
144+ /// </remarks>
117145 public bool TrySetPosition ( Vector2 position )
118146 {
119- if ( NativeBackend . WarpMouseGlobal ( position . X , position . Y ) )
147+ // make sure we get pumped mouse events for setting the position this way
148+ if ( ! _hintsAsEvents )
120149 {
121- SetTargetPoint ( null , new Vector3 ( position . X , position . Y , 0 ) , 0 ) ;
122- return true ;
150+ var currentHintVal = NativeBackend . GetHintBoolean ( Sdl . HintMouseRelativeWarpMotion , new MaybeBool < byte > ( 0 ) ) ;
151+ if ( currentHintVal == 0 )
152+ {
153+ sbyte hintVal = 1 ;
154+ if ( NativeBackend . SetHint ( Sdl . HintMouseRelativeWarpMotion , new Ref < sbyte > ( ref hintVal ) ) )
155+ {
156+ _hintsAsEvents = true ;
157+ }
158+ }
159+ else
160+ {
161+ _hintsAsEvents = true ;
162+ }
123163 }
124164
125- SdlLog . Error ( "Failed to set mouse position" ) ;
126- return false ;
165+ // providing a null window handle means that SDL will use the latest position
166+ NativeBackend . WarpMouseInWindow ( default , position . X , position . Y ) ;
167+ NeedsPump = true ; // we should "pump" events immediately, but this isn't a good place to do it.
168+ return true ;
127169 }
128170
129- public void AddMotion ( in MouseMotionEvent evtMotion )
130- {
131- var mouseWindowId = evtMotion . WindowID ;
132- var movementRelative = new Vector3 ( evtMotion . Xrel , evtMotion . Yrel , 0 ) ;
133- _accumulatedMotion += movementRelative ;
134- // todo - test against evtMotion state values
135-
136- //SetTargetPoint(mouseWindowId, _accumulatedMotion, 0, 0);
137- SetTargetPoint ( mouseWindowId , new Vector3 ( evtMotion . X , evtMotion . Y , 0 ) , 0 ) ;
138- }
171+ /// <summary>
172+ /// A user has attempted to modify the hardware mouse position - we need a pump to process these events.
173+ /// </summary>
174+ public bool NeedsPump { get ; private set ; }
139175
176+ public void AddMotion ( in MouseMotionEvent evtMotion ) =>
177+ AddOrUpdatePoint ( null , evtMotion . WindowID , new Vector3 ( evtMotion . X , evtMotion . Y , 0 ) , 1 , null , null ,
178+ evtMotion . WindowID != 0 ) ;
140179
141180
142181 public void AddButtonEvent ( in MouseButtonEvent evtButton )
143182 {
144183 var button = PointerButton . Primary + evtButton . Button ;
145184 const float mult = 1 / 255f ;
146- AddButtonEvent ( button , evtButton . Down > 0 , evtButton . Down * mult ) ;
185+ AddButtonEvent ( button , evtButton . Timestamp , evtButton . Down > 0 , evtButton . Down * mult ) ;
147186 }
148187
149188 public void AddWheelEvent ( in MouseWheelEvent evtWheel )
150189 {
151- ref var x = ref _mouseScroll . X ;
152- ref var y = ref _mouseScroll . Y ;
153- x += evtWheel . X ;
154- y += evtWheel . Y ;
155-
156- // todo - evt.Which?
157- var hMagnitude = MathF . Abs ( x ) ;
158- var vMagnitude = MathF . Abs ( y ) ;
159-
160- if ( hMagnitude >= 1 )
161- {
162- // horizontal scroll "tick"
163- _mouseScroll . X = 0 ;
164- }
165-
166- if ( vMagnitude >= 1 )
167- {
168- // vertical scroll "tick"
169- _mouseScroll . Y = 0 ;
170- }
171-
172- _state . WheelPosition = _mouseScroll ;
190+ var wheelState = _state . WheelPosition = new Vector2 ( evtWheel . X , evtWheel . Y ) ;
191+ AddMouseScrollEvent (
192+ scrollWheelPosition : wheelState ,
193+ windowId : evtWheel . WindowID ,
194+ position : new Vector3 ( evtWheel . MouseX , evtWheel . MouseY , 0 ) ,
195+ isMouseRelative : evtWheel . WindowID != 0 ) ;
173196 }
174-
175-
176- private Vector2 _mouseScroll ;
177- private Vector3 _accumulatedMotion ;
178197}
0 commit comments