Browse Source

Added support for high-DPI cursors and icons

Fixes https://github.com/libsdl-org/SDL/issues/9838
Sam Lantinga 1 year ago
parent
commit
31ed3665ad

+ 2 - 0
include/SDL3/SDL_mouse.h

@@ -413,6 +413,8 @@ extern SDL_DECLSPEC SDL_Cursor * SDLCALL SDL_CreateCursor(const Uint8 * data,
 /**
  * Create a color cursor.
  *
+ * If this function is passed a surface with alternate representations, the surface will be interpreted as the content to be used for 100% display scale, and the alternate representations will be used for high DPI situations. For example, if the original surface is 32x32, then on a 2x macOS display or 200% display scale on Windows, a 64x64 version of the image will be used, if available. If a matching version of the image isn't available, the closest size image will be scaled to the appropriate size and be used instead.
+ *
  * \param surface an SDL_Surface structure representing the cursor image.
  * \param hot_x the x position of the cursor hot spot.
  * \param hot_y the y position of the cursor hot spot.

+ 2 - 0
include/SDL3/SDL_video.h

@@ -1334,6 +1334,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetWindowTitle(SDL_Window *window);
 /**
  * Set the icon for a window.
  *
+ * If this function is passed a surface with alternate representations, the surface will be interpreted as the content to be used for 100% display scale, and the alternate representations will be used for high DPI situations. For example, if the original surface is 32x32, then on a 2x macOS display or 200% display scale on Windows, a 64x64 version of the image will be used, if available. If a matching version of the image isn't available, the closest size image will be scaled to the appropriate size and be used instead.
+ *
  * \param window the window to change.
  * \param icon an SDL_Surface structure containing the icon for the window.
  * \returns 0 on success or a negative error code on failure; call

+ 37 - 26
src/video/cocoa/SDL_cocoavideo.m

@@ -250,43 +250,54 @@ SDL_SystemTheme Cocoa_GetSystemTheme(void)
 /* This function assumes that it's called from within an autorelease pool */
 NSImage *Cocoa_CreateImage(SDL_Surface *surface)
 {
-    SDL_Surface *converted;
-    NSBitmapImageRep *imgrep;
-    Uint8 *pixels;
     NSImage *img;
 
-    converted = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
-    if (!converted) {
+    img = [[NSImage alloc] initWithSize:NSMakeSize(surface->w, surface->h)];
+    if (img == nil) {
         return nil;
     }
 
-    /* Premultiply the alpha channel */
-    SDL_PremultiplySurfaceAlpha(converted, SDL_FALSE);
-
-    imgrep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
-                                                     pixelsWide:converted->w
-                                                     pixelsHigh:converted->h
-                                                  bitsPerSample:8
-                                                samplesPerPixel:4
-                                                       hasAlpha:YES
-                                                       isPlanar:NO
-                                                 colorSpaceName:NSDeviceRGBColorSpace
-                                                    bytesPerRow:converted->pitch
-                                                   bitsPerPixel:SDL_BITSPERPIXEL(converted->format)];
-    if (imgrep == nil) {
-        SDL_DestroySurface(converted);
+    SDL_Surface **images = SDL_GetSurfaceImages(surface, NULL);
+    if (!images) {
         return nil;
     }
 
-    /* Copy the pixels */
-    pixels = [imgrep bitmapData];
-    SDL_memcpy(pixels, converted->pixels, (size_t)converted->h * converted->pitch);
-    SDL_DestroySurface(converted);
+    for (int i = 0; images[i]; ++i) {
+        SDL_Surface *converted = SDL_ConvertSurface(images[i], SDL_PIXELFORMAT_RGBA32);
+        if (!converted) {
+            SDL_free(images);
+            return nil;
+        }
 
-    img = [[NSImage alloc] initWithSize:NSMakeSize(surface->w, surface->h)];
-    if (img != nil) {
+        /* Premultiply the alpha channel */
+        SDL_PremultiplySurfaceAlpha(converted, SDL_FALSE);
+
+        NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
+                                                                           pixelsWide:converted->w
+                                                                           pixelsHigh:converted->h
+                                                                        bitsPerSample:8
+                                                                      samplesPerPixel:4
+                                                                             hasAlpha:YES
+                                                                             isPlanar:NO
+                                                                       colorSpaceName:NSDeviceRGBColorSpace
+                                                                          bytesPerRow:converted->pitch
+                                                                         bitsPerPixel:SDL_BITSPERPIXEL(converted->format)];
+        if (imgrep == nil) {
+            SDL_free(images);
+            SDL_DestroySurface(converted);
+            return nil;
+        }
+
+        /* Copy the pixels */
+        Uint8 *pixels = [imgrep bitmapData];
+        SDL_memcpy(pixels, converted->pixels, (size_t)converted->h * converted->pitch);
+        SDL_DestroySurface(converted);
+
+        /* Add the image representation */
         [img addRepresentation:imgrep];
     }
+    SDL_free(images);
+
     return img;
 }
 

+ 106 - 5
src/video/windows/SDL_windowsmouse.c

@@ -27,12 +27,24 @@
 #include "SDL_windowsrawinput.h"
 
 #include "../SDL_video_c.h"
+#include "../SDL_blit.h"
 #include "../../events/SDL_mouse_c.h"
 #include "../../joystick/usb_ids.h"
 
 
+typedef struct CachedCursor
+{
+    float scale;
+    HCURSOR cursor;
+    struct CachedCursor *next;
+} CachedCursor;
+
 struct SDL_CursorData
 {
+    SDL_Surface *surface;
+    int hot_x;
+    int hot_y;
+    CachedCursor *cache;
     HCURSOR cursor;
 };
 
@@ -189,6 +201,7 @@ static HCURSOR WIN_CreateHCursor(SDL_Surface *surface, int hot_x, int hot_y)
     ii.hbmColor = is_monochrome ? NULL : CreateColorBitmap(surface);
 
     if (!ii.hbmMask || (!is_monochrome && !ii.hbmColor)) {
+        SDL_SetError("Couldn't create cursor bitmaps");
         return NULL;
     }
 
@@ -208,11 +221,29 @@ static HCURSOR WIN_CreateHCursor(SDL_Surface *surface, int hot_x, int hot_y)
 
 static SDL_Cursor *WIN_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y)
 {
-    HCURSOR hcursor = WIN_CreateHCursor(surface, hot_x, hot_y);
-    if (!hcursor) {
-        return NULL;
+    if (!SDL_SurfaceHasAlternateImages(surface)) {
+        HCURSOR hcursor = WIN_CreateHCursor(surface, hot_x, hot_y);
+        if (!hcursor) {
+            return NULL;
+        }
+        return WIN_CreateCursorAndData(hcursor);
+    }
+
+    // Dynamically generate cursors at the appropriate DPI
+    SDL_Cursor *cursor = (SDL_Cursor *)SDL_calloc(1, sizeof(*cursor));
+    if (cursor) {
+        SDL_CursorData *data = (SDL_CursorData *)SDL_calloc(1, sizeof(*data));
+        if (!data) {
+            SDL_free(cursor);
+            return NULL;
+        }
+        data->hot_x = hot_x;
+        data->hot_y = hot_y;
+        data->surface = surface;
+        ++surface->refcount;
+        cursor->internal = data;
     }
-    return WIN_CreateCursorAndData(hcursor);
+    return cursor;
 }
 
 static SDL_Cursor *WIN_CreateBlankCursor(void)
@@ -302,6 +333,15 @@ static void WIN_FreeCursor(SDL_Cursor *cursor)
 {
     SDL_CursorData *data = cursor->internal;
 
+    if (data->surface) {
+        SDL_DestroySurface(data->surface);
+    }
+    while (data->cache) {
+        CachedCursor *entry = data->cache;
+        data->cache = entry->next;
+        DestroyCursor(entry->cursor);
+        SDL_free(entry);
+    }
     if (data->cursor) {
         DestroyCursor(data->cursor);
     }
@@ -309,13 +349,74 @@ static void WIN_FreeCursor(SDL_Cursor *cursor)
     SDL_free(cursor);
 }
 
+static HCURSOR GetCachedCursor(SDL_Cursor *cursor)
+{
+    SDL_CursorData *data = cursor->internal;
+
+    SDL_Window *focus = SDL_GetMouseFocus();
+    if (!focus) {
+        return NULL;
+    }
+
+    float scale = SDL_GetDisplayContentScale(SDL_GetDisplayForWindow(focus));
+    for (CachedCursor *entry = data->cache; entry; entry = entry->next) {
+        if (scale == entry->scale) {
+            return entry->cursor;
+        }
+    }
+
+    // Need to create a cursor for this content scale
+    SDL_Surface *surface = NULL;
+    HCURSOR hcursor = NULL;
+    CachedCursor *entry = NULL;
+
+    surface = SDL_GetSurfaceImage(data->surface, scale);
+    if (!surface) {
+        goto error;
+    }
+
+    int hot_x = (int)SDL_round(data->hot_x * scale);
+    int hot_y = (int)SDL_round(data->hot_x * scale);
+    hcursor = WIN_CreateHCursor(surface, hot_x, hot_y);
+    if (!hcursor) {
+        goto error;
+    }
+
+    entry = (CachedCursor *)SDL_malloc(sizeof(*entry));
+    if (!entry) {
+        goto error;
+    }
+    entry->cursor = hcursor;
+    entry->scale = scale;
+    entry->next = data->cache;
+    data->cache = entry;
+
+    SDL_DestroySurface(surface);
+
+    return hcursor;
+
+error:
+    if (surface) {
+        SDL_DestroySurface(surface);
+    }
+    if (hcursor) {
+        DestroyCursor(hcursor);
+    }
+    SDL_free(entry);
+    return NULL;
+}
+
 static int WIN_ShowCursor(SDL_Cursor *cursor)
 {
     if (!cursor) {
         cursor = SDL_blank_cursor;
     }
     if (cursor) {
-        SDL_cursor = cursor->internal->cursor;
+        if (cursor->internal->surface) {
+            SDL_cursor = GetCachedCursor(cursor);
+        } else {
+            SDL_cursor = cursor->internal->cursor;
+        }
     } else {
         SDL_cursor = NULL;
     }

BIN
test/icon2x.bmp


+ 47 - 5
test/testcustomcursor.c

@@ -109,10 +109,8 @@ static const char *cross[] = {
     "0,0"
 };
 
-static SDL_Cursor *
-init_color_cursor(const char *file)
+static SDL_Surface *load_image_file(const char *file)
 {
-    SDL_Cursor *cursor = NULL;
     SDL_Surface *surface = SDL_LoadBMP(file);
     if (surface) {
         if (SDL_GetSurfacePalette(surface)) {
@@ -138,14 +136,50 @@ init_color_cursor(const char *file)
                 break;
             }
         }
+    }
+    return surface;
+}
+
+static SDL_Surface *load_image(const char *file)
+{
+    SDL_Surface *surface = load_image_file(file);
+    if (surface) {
+        /* Add a 2x version of this image, if available */
+        SDL_Surface *surface2x = NULL;
+        const char *ext = SDL_strrchr(file, '.');
+        size_t len = SDL_strlen(file) + 2 + 1;
+        char *file2x = (char *)SDL_malloc(len);
+        if (file2x) {
+            SDL_strlcpy(file2x, file, len);
+            if (ext) {
+                SDL_memcpy(file2x + (ext - file), "2x", 3);
+                SDL_strlcat(file2x, ext, len);
+            } else {
+                SDL_strlcat(file2x, "2x", len);
+            }
+            surface2x = load_image_file(file2x);
+            SDL_free(file2x);
+        }
+        if (surface2x) {
+            SDL_AddSurfaceAlternateImage(surface, surface2x);
+            SDL_DestroySurface(surface2x);
+        }
+    }
+    return surface;
+}
+
+static SDL_Cursor *init_color_cursor(const char *file)
+{
+    SDL_Cursor *cursor = NULL;
+    SDL_Surface *surface = load_image(file);
+    if (surface) {
         cursor = SDL_CreateColorCursor(surface, 0, 0);
         SDL_DestroySurface(surface);
     }
     return cursor;
 }
 
-static SDL_Cursor *
-init_system_cursor(const char *image[])
+static SDL_Cursor *init_system_cursor(const char *image[])
 {
     int i, row, col;
     Uint8 data[4 * 32];
@@ -373,6 +407,14 @@ int main(int argc, char *argv[])
     num_cursors = 0;
 
     if (color_cursor) {
+        SDL_Surface *icon = load_image(color_cursor);
+        if (icon) {
+            for (i = 0; i < state->num_windows; ++i) {
+                SDL_SetWindowIcon(state->windows[i], icon);
+            }
+            SDL_DestroySurface(icon);
+        }
+
         cursor = init_color_cursor(color_cursor);
         if (cursor) {
             cursors[num_cursors] = cursor;