From bd86e8524962d04868c2607d92452e1745571c86 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 19 Oct 2025 08:57:51 -0700
Subject: [PATCH] Added fallback support for animated cursors
---
src/events/SDL_events.c | 2 +
src/events/SDL_mouse.c | 116 ++++++++++++++++++++++++++++++++++-----
src/events/SDL_mouse_c.h | 19 ++++++-
3 files changed, 123 insertions(+), 14 deletions(-)
diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c
index bf7cedaf2ce14..7a50c6681723a 100644
--- a/src/events/SDL_events.c
+++ b/src/events/SDL_events.c
@@ -1477,6 +1477,8 @@ void SDL_PumpEventMaintenance(void)
}
#endif
+ SDL_UpdateCursorAnimation();
+
SDL_UpdateTrays();
SDL_SendPendingSignalEvents(); // in case we had a signal handler fire, etc.
diff --git a/src/events/SDL_mouse.c b/src/events/SDL_mouse.c
index 9f6ae32819413..49b86fd96f66b 100644
--- a/src/events/SDL_mouse.c
+++ b/src/events/SDL_mouse.c
@@ -1552,6 +1552,79 @@ SDL_Cursor *SDL_CreateCursor(const Uint8 *data, const Uint8 *mask, int w, int h,
return cursor;
}
+static void SDL_DestroyCursorAnimation(SDL_CursorAnimation *animation)
+{
+ if (!animation) {
+ return;
+ }
+
+ for (int i = 0; i < animation->num_frames; ++i) {
+ SDL_DestroyCursor(animation->frames[i]);
+ }
+ SDL_free(animation->frames);
+ SDL_free(animation->durations);
+ SDL_free(animation);
+}
+
+static SDL_CursorAnimation *SDL_CreateCursorAnimation(SDL_CursorFrameInfo *frames, int frame_count, int hot_x, int hot_y)
+{
+ SDL_CursorAnimation *animation = (SDL_CursorAnimation *)SDL_calloc(1, sizeof(*animation));
+ if (!animation) {
+ return NULL;
+ }
+
+ animation->frames = (SDL_Cursor **)SDL_calloc(frame_count, sizeof(*animation->frames));
+ animation->durations = (Uint32 *)SDL_calloc(frame_count, sizeof(*animation->durations));
+ if (!animation->frames || !animation->durations) {
+ SDL_DestroyCursorAnimation(animation);
+ return NULL;
+ }
+
+ for (int i = 0; i < frame_count; ++i) {
+ animation->frames[i] = SDL_CreateColorCursor(frames[i].surface, hot_x, hot_y);
+ if (!animation->frames[i]) {
+ SDL_DestroyCursorAnimation(animation);
+ return NULL;
+ }
+
+ animation->durations[i] = frames[i].duration;
+ }
+ animation->num_frames = frame_count;
+
+ return animation;
+}
+
+void SDL_UpdateCursorAnimation(void)
+{
+ SDL_Mouse *mouse = SDL_GetMouse();
+ SDL_Cursor *cursor = mouse->cur_cursor;
+
+ if (!cursor || !cursor->animation) {
+ return;
+ }
+
+ if (!mouse->focus) {
+ return;
+ }
+
+ SDL_CursorAnimation *animation = cursor->animation;
+ Uint32 duration = animation->durations[animation->current_frame];
+ if (!duration) {
+ // We've reached the stop frame of the animation
+ return;
+ }
+
+ Uint64 now = SDL_GetTicks();
+ if (now < (animation->last_update + duration)) {
+ return;
+ }
+
+ animation->current_frame = (animation->current_frame + 1) % animation->num_frames;
+ animation->last_update = now;
+
+ SDL_RedrawCursor();
+}
+
SDL_Cursor *SDL_CreateAnimatedCursor(SDL_CursorFrameInfo *frames, int frame_count, int hot_x, int hot_y)
{
SDL_Mouse *mouse = SDL_GetMouse();
@@ -1572,18 +1645,6 @@ SDL_Cursor *SDL_CreateAnimatedCursor(SDL_CursorFrameInfo *frames, int frame_coun
return NULL;
}
- // Fall back to a static cursor if the platform doesn't support animated cursors.
- if (!mouse->CreateAnimatedCursor) {
- // If there is a frame with infinite duration, use it; otherwise, use the first.
- for (int i = 0; i < frame_count; ++i) {
- if (!frames[i].duration) {
- return SDL_CreateColorCursor(frames[i].surface, hot_x, hot_y);
- }
- }
-
- return SDL_CreateColorCursor(frames[0].surface, hot_x, hot_y);
- }
-
// Allow specifying the hot spot via properties on the surface
SDL_PropertiesID props = SDL_GetSurfaceProperties(frames[0].surface);
hot_x = (int)SDL_GetNumberProperty(props, SDL_PROP_SURFACE_HOTSPOT_X_NUMBER, hot_x);
@@ -1630,7 +1691,22 @@ SDL_Cursor *SDL_CreateAnimatedCursor(SDL_CursorFrameInfo *frames, int frame_coun
temp_frames[i].duration = frames[i].duration;
}
- cursor = mouse->CreateAnimatedCursor(temp_frames, frame_count, hot_x, hot_y);
+ if (mouse->CreateAnimatedCursor) {
+ cursor = mouse->CreateAnimatedCursor(temp_frames, frame_count, hot_x, hot_y);
+ } else {
+ SDL_CursorAnimation *animation = SDL_CreateCursorAnimation(temp_frames, frame_count, hot_x, hot_y);
+ if (!animation) {
+ goto cleanup;
+ }
+
+ cursor = (SDL_Cursor *)SDL_calloc(1, sizeof(*cursor));
+ if (!cursor) {
+ SDL_DestroyCursorAnimation(animation);
+ goto cleanup;
+ }
+ cursor->animation = animation;
+ }
+
if (cursor) {
cursor->next = mouse->cursors;
mouse->cursors = cursor;
@@ -1729,6 +1805,11 @@ void SDL_RedrawCursor(void)
cursor = NULL;
}
+ if (cursor && cursor->animation) {
+ SDL_CursorAnimation *animation = cursor->animation;
+ cursor = animation->frames[animation->current_frame];
+ }
+
if (mouse->ShowCursor) {
mouse->ShowCursor(cursor);
}
@@ -1761,6 +1842,11 @@ bool SDL_SetCursor(SDL_Cursor *cursor)
return SDL_SetError("Cursor not associated with the current mouse");
}
}
+ if (cursor->animation) {
+ SDL_CursorAnimation *animation = cursor->animation;
+ animation->current_frame = 0;
+ animation->last_update = SDL_GetTicks();
+ }
mouse->cur_cursor = cursor;
}
@@ -1814,6 +1900,10 @@ void SDL_DestroyCursor(SDL_Cursor *cursor)
mouse->cursors = curr->next;
}
+ if (curr->animation) {
+ SDL_DestroyCursorAnimation(curr->animation);
+ }
+
if (mouse->FreeCursor && curr->internal) {
mouse->FreeCursor(curr);
} else {
diff --git a/src/events/SDL_mouse_c.h b/src/events/SDL_mouse_c.h
index 6e049fe841921..30ec51d7b607c 100644
--- a/src/events/SDL_mouse_c.h
+++ b/src/events/SDL_mouse_c.h
@@ -31,10 +31,24 @@
typedef struct SDL_CursorData SDL_CursorData;
+struct SDL_Cursor;
+
+typedef struct
+{
+ Uint64 last_update;
+ int num_frames;
+ int current_frame;
+ struct SDL_Cursor **frames;
+ Uint32 *durations;
+} SDL_CursorAnimation;
+
struct SDL_Cursor
{
- struct SDL_Cursor *next;
+ SDL_CursorAnimation *animation;
+
SDL_CursorData *internal;
+
+ struct SDL_Cursor *next;
};
typedef struct
@@ -184,6 +198,9 @@ extern void SDL_SetDefaultCursor(SDL_Cursor *cursor);
// Get the preferred default system cursor
extern SDL_SystemCursor SDL_GetDefaultSystemCursor(void);
+// Update the current cursor animation if needed
+extern void SDL_UpdateCursorAnimation(void);
+
// Set the mouse focus window
extern void SDL_SetMouseFocus(SDL_Window *window);