Added support for high-DPI cursors and icons

Fixes https://github.com/libsdl-org/SDL/issues/9838
This commit is contained in:
Sam Lantinga
2024-08-01 11:38:17 -07:00
parent 94d9229ce2
commit 31ed3665ad
6 changed files with 200 additions and 42 deletions

View File

@@ -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) {
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);
return nil;
}
/* Copy the pixels */
pixels = [imgrep bitmapData];
SDL_memcpy(pixels, converted->pixels, (size_t)converted->h * converted->pitch);
SDL_DestroySurface(converted);
img = [[NSImage alloc] initWithSize:NSMakeSize(surface->w, surface->h)];
if (img != nil) {
if (img == nil) {
return nil;
}
SDL_Surface **images = SDL_GetSurfaceImages(surface, NULL);
if (!images) {
return nil;
}
for (int i = 0; images[i]; ++i) {
SDL_Surface *converted = SDL_ConvertSurface(images[i], SDL_PIXELFORMAT_RGBA32);
if (!converted) {
SDL_free(images);
return 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;
}

View File

@@ -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);
}
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 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;
}