Skip to content

Commit c509002

Browse files
committed
cursor api "complete" (untested)
1 parent 19f7c7c commit c509002

File tree

2 files changed

+182
-74
lines changed

2 files changed

+182
-74
lines changed

sources/Input/Input/Implementations/SDL3/Devices/Pointers/SdlCursor.cs

Lines changed: 148 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,26 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Collections.Frozen;
5-
using System.Runtime.CompilerServices;
65
using System.Runtime.InteropServices;
76
using Silk.NET.SDL;
87

98
namespace 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

sources/Input/Input/Implementations/SDL3/SdlLog.cs

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
namespace Silk.NET.Input.SDL3;
99

10-
internal class SdlLog
10+
internal static class InputLog
1111
{
1212
[Conditional("DEBUG")]
1313
public static void Error(string? message = null,
@@ -19,6 +19,11 @@ public static void Error(string? message = null,
1919
Console.Error.WriteLine(log);
2020
}
2121

22+
private static string GenerateLog(string? message, string? path, int line, string? member)
23+
{
24+
const string traceFmt = "{0} at {1}:{2}";
25+
return $"{message} ({string.Format(traceFmt, member, path, line)})";
26+
}
2227

2328
[Conditional("DEBUG")]
2429
public static void Debug(string? message = null,
@@ -27,27 +32,43 @@ public static void Debug(string? message = null,
2732
[CallerMemberName] string? member = null)
2833
{
2934
var log = GenerateLog(message, path, line, member);
30-
Console.Out.WriteLine(log);
35+
Console.WriteLine(log);
3136
}
37+
}
3238

33-
private static unsafe string GenerateLog(string? message, string? path, int line, string? member)
39+
internal static class SdlLog
40+
{
41+
[Conditional("DEBUG")]
42+
public static void Error(string? message = null,
43+
[CallerFilePath] string? path = null,
44+
[CallerLineNumber] int line = 0,
45+
[CallerMemberName] string? member = null)
46+
{
47+
var log = GenerateLog(message);
48+
InputLog.Error(log, path, line, member);
49+
}
50+
51+
52+
[Conditional("DEBUG")]
53+
public static void Debug(string? message = null,
54+
[CallerFilePath] string? path = null,
55+
[CallerLineNumber] int line = 0,
56+
[CallerMemberName] string? member = null)
57+
{
58+
var log = GenerateLog(message);
59+
InputLog.Debug(log, path, line, member);
60+
}
61+
62+
private static unsafe string GenerateLog(string? message)
3463
{
3564
var error = Sdl.GetError();
36-
string log;
37-
const string traceFmt = "{0} at {1}:{2}";
3865
if (error.Native != null)
3966
{
4067
var sdlError = error.ReadToString();
4168
Sdl.ClearError();
42-
message ??= "SDL Error: ";
43-
log = $"{message} {sdlError} ({string.Format(traceFmt, member, path, line)})";
44-
}
45-
else
46-
{
47-
message ??= "Error: ";
48-
log = $"{message} ({string.Format(traceFmt, member, path, line)})";
69+
return $"{message ?? "SDL Error: "} {sdlError}";
4970
}
5071

51-
return log;
72+
return message ?? "Error: ";
5273
}
5374
}

0 commit comments

Comments
 (0)