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

@ -413,6 +413,8 @@ extern SDL_DECLSPEC SDL_Cursor * SDLCALL SDL_CreateCursor(const Uint8 * data,
/** /**
* Create a color cursor. * 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 surface an SDL_Surface structure representing the cursor image.
* \param hot_x the x position of the cursor hot spot. * \param hot_x the x position of the cursor hot spot.
* \param hot_y the y position of the cursor hot spot. * \param hot_y the y position of the cursor hot spot.

View File

@ -1334,6 +1334,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetWindowTitle(SDL_Window *window);
/** /**
* Set the icon for a 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 window the window to change.
* \param icon an SDL_Surface structure containing the icon for the window. * \param icon an SDL_Surface structure containing the icon for the window.
* \returns 0 on success or a negative error code on failure; call * \returns 0 on success or a negative error code on failure; call

View File

@ -250,43 +250,54 @@ SDL_SystemTheme Cocoa_GetSystemTheme(void)
/* This function assumes that it's called from within an autorelease pool */ /* This function assumes that it's called from within an autorelease pool */
NSImage *Cocoa_CreateImage(SDL_Surface *surface) NSImage *Cocoa_CreateImage(SDL_Surface *surface)
{ {
SDL_Surface *converted;
NSBitmapImageRep *imgrep;
Uint8 *pixels;
NSImage *img; 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)]; 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]; [img addRepresentation:imgrep];
} }
SDL_free(images);
return img; return img;
} }

View File

@ -27,12 +27,24 @@
#include "SDL_windowsrawinput.h" #include "SDL_windowsrawinput.h"
#include "../SDL_video_c.h" #include "../SDL_video_c.h"
#include "../SDL_blit.h"
#include "../../events/SDL_mouse_c.h" #include "../../events/SDL_mouse_c.h"
#include "../../joystick/usb_ids.h" #include "../../joystick/usb_ids.h"
typedef struct CachedCursor
{
float scale;
HCURSOR cursor;
struct CachedCursor *next;
} CachedCursor;
struct SDL_CursorData struct SDL_CursorData
{ {
SDL_Surface *surface;
int hot_x;
int hot_y;
CachedCursor *cache;
HCURSOR cursor; 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); ii.hbmColor = is_monochrome ? NULL : CreateColorBitmap(surface);
if (!ii.hbmMask || (!is_monochrome && !ii.hbmColor)) { if (!ii.hbmMask || (!is_monochrome && !ii.hbmColor)) {
SDL_SetError("Couldn't create cursor bitmaps");
return NULL; 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) static SDL_Cursor *WIN_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y)
{ {
HCURSOR hcursor = WIN_CreateHCursor(surface, hot_x, hot_y); if (!SDL_SurfaceHasAlternateImages(surface)) {
if (!hcursor) { HCURSOR hcursor = WIN_CreateHCursor(surface, hot_x, hot_y);
return NULL; 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) static SDL_Cursor *WIN_CreateBlankCursor(void)
@ -302,6 +333,15 @@ static void WIN_FreeCursor(SDL_Cursor *cursor)
{ {
SDL_CursorData *data = cursor->internal; 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) { if (data->cursor) {
DestroyCursor(data->cursor); DestroyCursor(data->cursor);
} }
@ -309,13 +349,74 @@ static void WIN_FreeCursor(SDL_Cursor *cursor)
SDL_free(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) static int WIN_ShowCursor(SDL_Cursor *cursor)
{ {
if (!cursor) { if (!cursor) {
cursor = SDL_blank_cursor; cursor = SDL_blank_cursor;
} }
if (cursor) { if (cursor) {
SDL_cursor = cursor->internal->cursor; if (cursor->internal->surface) {
SDL_cursor = GetCachedCursor(cursor);
} else {
SDL_cursor = cursor->internal->cursor;
}
} else { } else {
SDL_cursor = NULL; SDL_cursor = NULL;
} }

BIN
test/icon2x.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -109,10 +109,8 @@ static const char *cross[] = {
"0,0" "0,0"
}; };
static SDL_Cursor * static SDL_Surface *load_image_file(const char *file)
init_color_cursor(const char *file)
{ {
SDL_Cursor *cursor = NULL;
SDL_Surface *surface = SDL_LoadBMP(file); SDL_Surface *surface = SDL_LoadBMP(file);
if (surface) { if (surface) {
if (SDL_GetSurfacePalette(surface)) { if (SDL_GetSurfacePalette(surface)) {
@ -138,14 +136,50 @@ init_color_cursor(const char *file)
break; 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); cursor = SDL_CreateColorCursor(surface, 0, 0);
SDL_DestroySurface(surface); SDL_DestroySurface(surface);
} }
return cursor; return cursor;
} }
static SDL_Cursor * static SDL_Cursor *init_system_cursor(const char *image[])
init_system_cursor(const char *image[])
{ {
int i, row, col; int i, row, col;
Uint8 data[4 * 32]; Uint8 data[4 * 32];
@ -373,6 +407,14 @@ int main(int argc, char *argv[])
num_cursors = 0; num_cursors = 0;
if (color_cursor) { 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); cursor = init_color_cursor(color_cursor);
if (cursor) { if (cursor) {
cursors[num_cursors] = cursor; cursors[num_cursors] = cursor;