22// The .NET Foundation licenses this file to you under the MIT license.
33
44using System . Collections . Frozen ;
5- using System . Runtime . CompilerServices ;
65using System . Runtime . InteropServices ;
76using Silk . NET . SDL ;
87
98namespace Silk . NET . Input . SDL3 . Devices . Pointers ;
109
11- internal class SdlCursor : ICursorConfiguration , IDisposable
10+ internal unsafe class SdlCursor : ICursorConfiguration , IDisposable
1211{
1312 private readonly ISdl _sdl ;
13+
1414 private CursorHandle _handle ;
1515
16+ /// <summary>
17+ /// Internal style of the current cursor handle - may differ from the <see cref="Style"/> property,
18+ /// </summary>
19+ private CursorStyles _handleStyle = _noStyle ;
20+ private const CursorStyles _noStyle = ( CursorStyles ) ( - 1 ) ;
21+
1622 private static readonly FrozenDictionary < CursorStyles , SystemCursor > _cursorStyles =
1723 new Dictionary < CursorStyles , SystemCursor > {
24+ [ CursorStyles . Default ] = SystemCursor . Default ,
1825 [ CursorStyles . Arrow ] = SystemCursor . Default ,
1926 [ CursorStyles . IBeam ] = SystemCursor . Text ,
2027 [ CursorStyles . Crosshair ] = SystemCursor . Crosshair ,
@@ -24,79 +31,99 @@ internal class SdlCursor : ICursorConfiguration, IDisposable
2431 } . ToFrozenDictionary ( ) ;
2532
2633
27- public unsafe SdlCursor ( ISdl sdl )
34+ public SdlCursor ( ISdl sdl )
2835 {
2936 _sdl = sdl ;
3037 Mode = CursorModes . Normal ;
3138 SupportedStyles = TestCursorCompatibility ( sdl ) ;
32-
33- SetCursorStyle ( CursorStyles . Arrow ) ;
39+ Style = CursorStyles . Arrow ;
3440 }
3541
3642 private bool SetCursorStyle ( CursorStyles style )
3743 {
38- FreeCurrentCursor ( ) ;
44+ CursorHandle handle ;
3945 if ( style == CursorStyles . Custom )
4046 {
41- // to be set by the Image property, so we just return
42- if ( _customCursorImage != null )
47+ if ( _customCursorImage == null || _customCursorSurface == null )
4348 {
44- ApplyCustomCursor ( ) ;
49+ return false ;
4550 }
51+
52+ var canReuseCurrentCursorHandle = _handleStyle == CursorStyles . Custom ; // todo - compare cursor hotspot
53+ if ( canReuseCurrentCursorHandle )
54+ {
55+ return true ;
56+ }
57+
58+ // todo: cursor hotspot, not supported by sdl?
59+ handle = _sdl . CreateColorCursor ( _customCursorSurface , 0 , 0 ) ;
60+ }
61+ else if ( style == _handleStyle )
62+ {
4663 return true ;
4764 }
65+ else
66+ {
67+ handle = _sdl . CreateSystemCursor ( _cursorStyles [ style ] ) ;
68+ }
4869
49- _handle = _sdl . CreateSystemCursor ( _cursorStyles [ style ] ) ;
50- if ( _handle == default )
70+ if ( handle . Handle == null )
5171 {
52- SdlLog . Error ( "Failed to create system cursor" ) ;
72+ SdlLog . Error ( "Failed to create cursor" ) ;
5373 return false ;
5474 }
5575
76+ if ( _handle != handle )
77+ {
78+ FreeCurrentCursor ( ) ;
79+ }
80+
81+ _handle = handle ;
82+ _handleStyle = style ;
83+
5684 if ( _sdl . SetCursor ( _handle ) )
5785 {
58- Style = CursorStyles . Arrow ;
5986 return true ;
6087 }
6188
89+ SdlLog . Error ( "Failed to set cursor" ) ;
6290 return false ;
6391 }
6492
65- private void ApplyCustomCursor ( )
93+ public void Dispose ( )
6694 {
67- if ( _customCursorImage == null )
95+ FreeCurrentCursor ( ) ;
96+ DisposeCursorSurface ( ref _customCursorSurface ) ;
97+ }
98+
99+ private void FreeCurrentCursor ( )
100+ {
101+ if ( _handle == default )
68102 {
69- // presumably this was set by the Styles property, and we're still waiting on the image
70103 return ;
71104 }
72105
73- var image = _customCursorImage ;
74- var width = _customCursorWidth ;
75- var height = _customCursorHeight ;
76- var minSize = width * height * 4 ;
106+ _sdl . DestroyCursor ( _handle ) ;
107+ _handle = default ;
77108
78- if ( image . Length < minSize )
109+ if ( _handleStyle == CursorStyles . Custom )
79110 {
80- throw new ArgumentException ( $ "Custom cursor image of size ({ width } , { height } ) must be at least { minSize } " +
81- $ "bytes long, got { image . Length } bytes instead") ;
111+ DisposeCursorSurface ( ref _customCursorSurface ) ;
82112 }
83- }
84113
85- public void Dispose ( )
86- {
87- FreeCurrentCursor ( ) ;
114+ _handleStyle = _noStyle ;
88115 }
89116
90- private void FreeCurrentCursor ( )
117+ private void DisposeCursorSurface ( ref Surface * surface )
91118 {
92- if ( _handle != default )
119+ if ( surface != null )
93120 {
94- _sdl . DestroyCursor ( _handle ) ;
95- _handle = default ;
121+ _sdl . DestroySurface ( surface ) ;
122+ _customCursorSurface = null ;
96123 }
97124 }
98125
99- private static unsafe CursorStyles TestCursorCompatibility ( ISdl sdl )
126+ private static CursorStyles TestCursorCompatibility ( ISdl sdl )
100127 {
101128 // check cursor style availability
102129 ReadOnlySpan < CursorStyles > mainStyles = [
@@ -130,7 +157,24 @@ private static unsafe CursorStyles TestCursorCompatibility(ISdl sdl)
130157 public CursorModes SupportedModes =>
131158 CursorModes . Normal | CursorModes . Confined | CursorModes . Unbounded ;
132159
133- public CursorModes Mode { get ; set ; }
160+ public CursorModes Mode
161+ {
162+ get ;
163+ set
164+ {
165+ field = value ;
166+ try
167+ {
168+ ModeChanged ? . Invoke ( this , value ) ;
169+ }
170+ catch ( Exception e )
171+ {
172+ InputLog . Error ( e . ToString ( ) ) ;
173+ }
174+ }
175+ }
176+
177+ public event EventHandler < CursorModes > ? ModeChanged ;
134178
135179 public CursorStyles SupportedStyles { get ; }
136180
@@ -139,33 +183,37 @@ public CursorStyles Style
139183 get ;
140184 set
141185 {
142- if ( value == field )
186+ if ( value == CursorStyles . Hidden && field != CursorStyles . Hidden )
143187 {
188+ SetCursorVisibility ( false ) ;
144189 return ;
145190 }
146191
147- if ( value == CursorStyles . Hidden )
148- {
149- if ( ! _sdl . HideCursor ( ) )
150- {
151- SdlLog . Error ( "Failed to hide cursor" ) ;
152- return ;
153- }
154- }
155- else if ( field == CursorStyles . Hidden )
192+ SetCursorStyle ( value ) ;
193+ if ( field == CursorStyles . Hidden )
156194 {
157- // reveal the cursor
158- if ( ! _sdl . ShowCursor ( ) )
159- {
160- SdlLog . Error ( "Failed to show cursor" ) ;
161- }
195+ SetCursorVisibility ( true ) ;
162196 }
163197
164- SetCursorStyle ( value ) ;
165198 field = value ;
166199 }
167200 }
168201
202+ private void SetCursorVisibility ( bool visible )
203+ {
204+ if ( _handle == default )
205+ {
206+ return ;
207+ }
208+
209+ if ( visible ? _sdl . HideCursor ( ) : _sdl . ShowCursor ( ) )
210+ {
211+ return ;
212+ }
213+
214+ SdlLog . Error ( "Failed to hide cursor" ) ;
215+ }
216+
169217 public CustomCursor Image
170218 {
171219 get
@@ -177,31 +225,70 @@ public CustomCursor Image
177225 }
178226 set
179227 {
180- var val = value ;
181- var necessaryLength = val . Width * val . Height ;
182- if ( val . Data . Length < necessaryLength )
228+ var necessaryLength = value . Width * value . Height ;
229+ if ( value . Data . Length < necessaryLength )
183230 {
184- throw new ArgumentException ( $ "Custom cursor image of size ({ val . Width } , { val . Height } ) " +
185- $ "must be at least { val . Width * val . Height * 4 } bytes long, " +
186- $ "got { val . Data . Length } bytes instead") ;
231+ throw new ArgumentException ( $ "Custom cursor image of size ({ value . Width } , { value . Height } ) " +
232+ $ "must be at least { value . Width * value . Height * 4 } bytes long, " +
233+ $ "got { value . Data . Length } bytes instead") ;
187234 }
188235
189- _customCursorHeight = val . Height ;
190- _customCursorWidth = val . Width ;
236+ // ensure we have a fixed byte array to work with so updates would automatically apply to sdl
237+ _customCursorHeight = value . Height ;
238+ _customCursorWidth = value . Width ;
191239 var byteCount = necessaryLength * 4 ;
192240 if ( _customCursorImage is null || _customCursorImage . Length < byteCount )
193241 {
194- _customCursorImage = new byte [ byteCount ] ;
242+ _customCursorImage = GC . AllocateUninitializedArray < byte > ( byteCount , pinned : true ) ;
195243 }
196244
245+ // copy the user data to our fixed array
197246 var myBytes = _customCursorImage . AsSpan ( ..byteCount ) ;
198- var bytesAsInts = MemoryMarshal . Cast < byte , int > ( myBytes ) ;
199- value . Data [ .. necessaryLength ] . CopyTo ( bytesAsInts ) ;
247+ var providedBytes = MemoryMarshal . Cast < int , byte > ( value . Data ) ;
248+ providedBytes . CopyTo ( myBytes ) ;
200249
201- // todo - actually apply to sdl
250+ ApplyToCursorSurface ( ref _customCursorSurface , value ) ;
251+
252+ if ( Style == CursorStyles . Custom && _handleStyle != CursorStyles . Custom )
253+ {
254+ SetCursorStyle ( CursorStyles . Custom ) ;
255+ }
256+
257+ return ;
258+
259+ void ApplyToCursorSurface ( ref Surface * customCursorSurface , in CustomCursor val )
260+ {
261+ // create a new sdl surface if necessary
262+ if ( customCursorSurface != null )
263+ {
264+ if ( customCursorSurface ->H != val . Height || customCursorSurface ->W != val . Width )
265+ {
266+ DisposeCursorSurface ( ref customCursorSurface ) ;
267+ customCursorSurface = CreateSurface ( val ) ;
268+ }
269+ }
270+ else
271+ {
272+ customCursorSurface = _sdl . CreateSurface ( val . Width , val . Height , PixelFormat . Argb8888 ) ;
273+ }
274+
275+ // ensure the surface's pixel data is our fixed array
276+ fixed ( byte * ptr = _customCursorImage )
277+ {
278+ customCursorSurface ->Pixels = ptr ;
279+ }
280+
281+ return ;
282+
283+ Ptr < Surface > CreateSurface ( CustomCursor customCursor )
284+ {
285+ return _sdl . CreateSurface ( customCursor . Width , customCursor . Height , PixelFormat . Argb8888 ) ;
286+ }
287+ }
202288 }
203289 }
204290
291+ private Surface * _customCursorSurface ;
205292 private int _customCursorHeight , _customCursorWidth ;
206293 private byte [ ] ? _customCursorImage ;
207294
0 commit comments