Skip to content

Commit f3eb474

Browse files
[Rendering] Add Cursor SetCursor/TryCreateCursorImage;
1 parent 7217997 commit f3eb474

File tree

7 files changed

+164
-1
lines changed

7 files changed

+164
-1
lines changed

Engine/Core/Input/Cursor.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
namespace Staple;
44

5+
/// <summary>
6+
/// Mouse Cursor management class
7+
/// </summary>
58
public static class Cursor
69
{
710
internal static IRenderWindow window;
@@ -91,4 +94,33 @@ internal static void ShowCursor()
9194
{
9295
window.ShowCursor();
9396
}
97+
98+
/// <summary>
99+
/// Sets the mouse cursor to a specific image
100+
/// </summary>
101+
/// <param name="pixels">The image pixels</param>
102+
/// <param name="width">The width of the image</param>
103+
/// <param name="height">The height of the image</param>
104+
/// <param name="hotX">The x position for the cursor hotspot</param>
105+
/// <param name="hotY">The y position for the cursor hotspot</param>
106+
public static bool TryCreateCursorImage(Color32[] pixels, int width, int height, int hotX, int hotY, out CursorImage image)
107+
{
108+
if(pixels.Length != width * height)
109+
{
110+
image = default;
111+
112+
return false;
113+
}
114+
115+
return window.TryCreateCursorImage(pixels, width, height, hotX, hotY, out image);
116+
}
117+
118+
/// <summary>
119+
/// Changes the current cursor
120+
/// </summary>
121+
/// <param name="image">The cursor image. If it's null, resets to the arrow cursor</param>
122+
public static void SetCursor(CursorImage image)
123+
{
124+
window.SetCursor(image);
125+
}
94126
}

Engine/Core/Input/CursorImage.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System;
2+
3+
namespace Staple;
4+
5+
/// <summary>
6+
/// Represents an image for a cursor
7+
/// </summary>
8+
public abstract class CursorImage
9+
{
10+
/// <summary>
11+
/// Disposes the resources of this cursor
12+
/// </summary>
13+
public abstract void Dispose();
14+
}

Engine/Core/Rendering/Windowing/IRenderWindow.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ bool Create(ref int width, ref int height, string title, bool resizable, WindowM
4343

4444
void HideCursor();
4545

46+
bool TryCreateCursorImage(Color32[] pixels, int width, int height, int hotX, int hotY, out CursorImage image);
47+
48+
void SetCursor(CursorImage image);
49+
4650
void SetIcon(RawTextureData icon);
4751

4852
bool SetResolution(int width, int height, WindowMode windowMode);

Engine/Core/Rendering/Windowing/Impls/AndroidRenderWindow.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,5 +172,16 @@ public bool SetResolution(int width, int height, WindowMode windowMode)
172172
{
173173
return false;
174174
}
175+
176+
public bool TryCreateCursorImage(Color32[] pixels, int width, int height, int hotX, int hotY, out CursorImage image)
177+
{
178+
image = default;
179+
180+
return false;
181+
}
182+
183+
public void SetCursor(CursorImage image)
184+
{
185+
}
175186
}
176187
#endif

Engine/Core/Rendering/Windowing/Impls/SDL2RenderWindow.cs

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,50 @@ internal class SDL2RenderWindow : IRenderWindow
1111
{
1212
private const short AxisDeadzone = 8000;
1313

14+
private class SDL2Cursor : CursorImage
15+
{
16+
public Color32[] pixels;
17+
public nint surface = nint.Zero;
18+
public nint cursor = nint.Zero;
19+
20+
public override void Dispose()
21+
{
22+
if (cursor != nint.Zero)
23+
{
24+
SDL.SDL_FreeCursor(cursor);
25+
26+
cursor = nint.Zero;
27+
}
28+
29+
if(surface != nint.Zero)
30+
{
31+
SDL.SDL_FreeSurface(surface);
32+
33+
surface = nint.Zero;
34+
}
35+
36+
pixels = [];
37+
}
38+
}
39+
1440
private class GamepadState
1541
{
1642
public nint instance;
1743
}
1844

1945
public nint window;
2046

21-
private Dictionary<int, GamepadState> gamepads = new();
47+
private readonly Dictionary<int, GamepadState> gamepads = [];
48+
49+
private readonly List<SDL2Cursor> cursors = [];
2250

2351
private bool movedWindow = false;
2452
private DateTime movedWindowTimer;
2553
private Vector2Int previousWindowPosition;
2654
private bool closedWindow = false;
2755
private bool windowFocused = true;
2856
private bool windowMaximized = false;
57+
private nint defaultCursor = nint.Zero;
2958

3059
private nint metalView = nint.Zero;
3160

@@ -142,11 +171,18 @@ public bool Create(ref int width, ref int height, string title, bool resizable,
142171
windowMaximized = true;
143172
}
144173

174+
defaultCursor = SDL.SDL_CreateSystemCursor(SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_ARROW);
175+
145176
return true;
146177
}
147178

148179
public void Destroy()
149180
{
181+
foreach (var cursor in cursors)
182+
{
183+
cursor.Dispose();
184+
}
185+
150186
SDL.SDL_DestroyWindow(window);
151187
}
152188

@@ -755,5 +791,59 @@ public bool SetResolution(int width, int height, WindowMode windowMode)
755791

756792
return true;
757793
}
794+
795+
public bool TryCreateCursorImage(Color32[] pixels, int width, int height, int hotX, int hotY, out CursorImage image)
796+
{
797+
unsafe
798+
{
799+
var outValue = new SDL2Cursor
800+
{
801+
pixels = pixels
802+
};
803+
804+
fixed (void *ptr = outValue.pixels)
805+
{
806+
var surface = SDL.SDL_CreateRGBSurfaceFrom((nint)ptr, width, height, 32, width * 4, 0, 0, 0, 0);
807+
808+
if(surface == nint.Zero)
809+
{
810+
image = default;
811+
812+
return false;
813+
}
814+
815+
var cursor = SDL.SDL_CreateColorCursor(surface, hotX, hotY);
816+
817+
if(cursor == nint.Zero)
818+
{
819+
SDL.SDL_FreeSurface(surface);
820+
821+
image = default;
822+
823+
return false;
824+
}
825+
826+
outValue.surface = surface;
827+
outValue.cursor = cursor;
828+
829+
image = outValue;
830+
831+
return true;
832+
}
833+
}
834+
}
835+
836+
public void SetCursor(CursorImage image)
837+
{
838+
if(image is not SDL2Cursor cursor ||
839+
cursor.cursor == nint.Zero)
840+
{
841+
SDL.SDL_SetCursor(defaultCursor);
842+
843+
return;
844+
}
845+
846+
SDL.SDL_SetCursor(cursor.cursor);
847+
}
758848
}
759849
#endif

Engine/Core/Rendering/Windowing/Impls/iOSRenderWindow.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,5 +174,16 @@ public bool SetResolution(int width, int height, WindowMode windowMode)
174174
{
175175
return false;
176176
}
177+
178+
public bool TryCreateCursorImage(Color32[] pixels, int width, int height, int hotX, int hotY, out ICursorImage image)
179+
{
180+
image = default;
181+
182+
return false;
183+
}
184+
185+
public void SetCursor(ICursorImage image)
186+
{
187+
}
177188
}
178189
#endif

Engine/Core/StapleCore.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@
140140
<Compile Include="Hooks\IStapleHook.cs" />
141141
<Compile Include="Hooks\StapleHookEvent.cs" />
142142
<Compile Include="Input\Cursor.cs" />
143+
<Compile Include="Input\CursorImage.cs" />
143144
<Compile Include="Input\CursorLockMode.cs" />
144145
<Compile Include="Input\GamepadAxis.cs" />
145146
<Compile Include="Input\GamepadButton.cs" />

0 commit comments

Comments
 (0)