Update glfwSetWindowIcon to support application icons and icon size queries

New MacOS implementation for glfwSetWindowIcon
New test program: animate-icon
Documentation updates for glfwSetWindowIcon
This commit is contained in:
ws909 2023-03-09 16:30:43 +01:00
parent 9a87635686
commit 6381d7ed20
11 changed files with 351 additions and 64 deletions

View File

@ -98,6 +98,7 @@ video tutorials.
- IntellectualKitty
- Aaron Jacobs
- JannikGM
- Andreas O. Jansen
- Erik S. V. Jansson
- jjYBdx4IL
- Toni Jovanoski

View File

@ -121,6 +121,7 @@ information on what to include when reporting a bug.
## Changelog
- Updated `glfwSetWindowIcon` to set the MacOS Dock icon, and query for an optimal image size (#2041)
- Added `GLFW_PLATFORM` init hint for runtime platform selection (#1958)
- Added `GLFW_ANY_PLATFORM`, `GLFW_PLATFORM_WIN32`, `GLFW_PLATFORM_COCOA`,
`GLFW_PLATFORM_WAYLAND`, `GLFW_PLATFORM_X11` and `GLFW_PLATFORM_NULL` symbols to

View File

@ -902,6 +902,23 @@ sequential rows, starting from the top-left corner.
To revert to the default window icon, pass in an empty image array.
On Windows and X11, the window's title bar and taskbar icons are set with this method.
On MacOS, a `NULL` window handle may be passed to set the application's Dock icon.
Some platforms support querying an optimal size for its icons.
You can ask for this size by passing a `NULL` window handle, a count of zero,
and a valid pointer to an image with its `pixels` field set to `NULL`:
@code
GLFWimage image = { DEFAULT_WIDTH, DEFAULT_HEIGHT, NULL };
glfwSetWindowIcon(NULL, 0, &image);
image.pixels = generatePixels(image.width, image.height);
glfwSetWindowIcon(window, 1, &image);
free(image.pixels);
@endcode
@code
glfwSetWindowIcon(window, 0, NULL);
@endcode

View File

@ -3256,12 +3256,12 @@ GLFWAPI void glfwSetWindowShouldClose(GLFWwindow* window, int value);
*/
GLFWAPI void glfwSetWindowTitle(GLFWwindow* window, const char* title);
/*! @brief Sets the icon for the specified window.
/*! @brief Sets the icon for the specified window or application.
*
* This function sets the icon of the specified window. If passed an array of
* candidate images, those of or closest to the sizes desired by the system are
* selected. If no images are specified, the window reverts to its default
* icon.
* This function sets the icon of the specified window or application. If passed an
* array of candidate images, those of or closest to the sizes desired by the system
* are selected. If no images are specified, the window or application reverts to its
* default icon.
*
* The pixels are 32-bit, little-endian, non-premultiplied RGBA, i.e. eight
* bits per channel with the red channel first. They are arranged canonically
@ -3269,9 +3269,17 @@ GLFWAPI void glfwSetWindowTitle(GLFWwindow* window, const char* title);
*
* The desired image sizes varies depending on platform and system settings.
* The selected images will be rescaled as needed. Good sizes include 16x16,
* 32x32 and 48x48.
* 32x32, 48x48, 64x64 and 128x128.
*
* @param[in] window The window whose icon to set.
* If passed a `NULL` window handle, a count of zero, and a valid pointer to an
* image with its `pixels` field set to `NULL`, GLFW will set that image's
* `width` and `height` fields to the optimal icon size for the current platform,
* if these can be retrieved. If they can not be retrieved by GLFW, they are left
* untouched. Doing this requires an internal const-cast for the `images` parameter.
* This parameter remains qualified with `const` for backwards-compatability.
* See @ref window_icon for an example.
*
* @param[in] window The window whose icon to set, or `NULL` for the application's icon.
* @param[in] count The number of images in the specified array, or zero to
* revert to the default window icon.
* @param[in] images The images to create the icon from. This is ignored if
@ -3284,13 +3292,22 @@ GLFWAPI void glfwSetWindowTitle(GLFWwindow* window, const char* title);
* @pointer_lifetime The specified image data is copied before this function
* returns.
*
* @remark @macos Regular windows do not have icons on macOS. This function
* will emit @ref GLFW_FEATURE_UNAVAILABLE. The dock icon will be the same as
* the application bundle's icon. For more information on bundles, see the
* [Bundle Programming Guide](https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFBundles/)
* @remark Every platform has either a window icon, or an application icon.
* To cover all platforms, you need to set both.
*
* @remark @macos The Dock icon defaults to the application bundle's icon.
* For more information on bundles, see the [Bundle Programming Guide](https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFBundles/)
* in the Mac Developer Library.
*
* @remark @wayland There is no existing protocol to change an icon, the
* @remark @wayland @x11 The default icon is specified in the application's
* desktop file.
*
* @remark @macos Regular windows do not have icons on macOS. This function
* will emit @ref GLFW_FEATURE_UNAVAILABLE if a valid window handle is passed.
* Pass a `NULL` window handle to set the Dock icon. Otherwise, the dock icon
* will be the same as the application bundle's icon.
*
* @remark @wayland There is no existing protocol to change an icon; the
* window will thus inherit the one defined in the application's desktop file.
* This function will emit @ref GLFW_FEATURE_UNAVAILABLE.
*

View File

@ -193,6 +193,35 @@ static NSUInteger translateKeyToModifierFlag(int key)
return 0;
}
// Converts a GLFWimage to an NSImage. The returned image must be explicitly freed.
//
static NSImage* imageToNative(const GLFWimage* image)
{
NSBitmapImageRep* representation = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:NULL
pixelsWide:image->width
pixelsHigh:image->height
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSCalibratedRGBColorSpace
bitmapFormat:NSBitmapFormatAlphaNonpremultiplied
bytesPerRow:image->width * 4
bitsPerPixel:32];
if (representation == nil)
return nil;
memcpy([representation bitmapData], image->pixels, image->width * image->height * 4);
NSImage* native = [[NSImage alloc] initWithSize:NSMakeSize(image->width, image->height)];
[native addRepresentation:representation];
[representation release];
return native;
}
// Defines a constant for empty ranges in NSTextInputClient
//
static const NSRange kEmptyRange = { NSNotFound, 0 };
@ -1028,8 +1057,44 @@ void _glfwSetWindowTitleCocoa(_GLFWwindow* window, const char* title)
void _glfwSetWindowIconCocoa(_GLFWwindow* window,
int count, const GLFWimage* images)
{
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
"Cocoa: Regular windows do not have icons on macOS");
if (window != NULL)
{
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
"Cocoa: Regular windows do not have icons on macOS");
return;
}
// This is the actual pixel storage size of the dock, which is what we're after here.
NSSize preferredSize = [NSApp dockTile].size;
if (count == 0 && images != NULL && images->pixels == NULL)
{
// Const-cast
((GLFWimage*) images)->width = preferredSize.width;
((GLFWimage*) images)->height = preferredSize.height;
return;
}
if (count == 0)
{
NSApp.applicationIconImage = nil;
return;
}
const GLFWimage* image = _glfwChooseImage(count, images, preferredSize.width, preferredSize.height);
assert(image->pixels != NULL);
NSImage* native = imageToNative(image);
if (native == nil)
{
_glfwInputError(GLFW_PLATFORM_ERROR, NULL);
return;
}
NSApp.applicationIconImage = native;
[native release];
}
void _glfwGetWindowPosCocoa(_GLFWwindow* window, int* xpos, int* ypos)
@ -1710,35 +1775,15 @@ GLFWbool _glfwCreateCursorCocoa(_GLFWcursor* cursor,
{
@autoreleasepool {
NSImage* native;
NSBitmapImageRep* rep;
rep = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:NULL
pixelsWide:image->width
pixelsHigh:image->height
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSCalibratedRGBColorSpace
bitmapFormat:NSBitmapFormatAlphaNonpremultiplied
bytesPerRow:image->width * 4
bitsPerPixel:32];
if (rep == nil)
NSImage* native = imageToNative(image);
if (native == nil)
return GLFW_FALSE;
memcpy([rep bitmapData], image->pixels, image->width * image->height * 4);
native = [[NSImage alloc] initWithSize:NSMakeSize(image->width, image->height)];
[native addRepresentation:rep];
cursor->ns.object = [[NSCursor alloc] initWithImage:native
hotSpot:NSMakePoint(xhot, yhot)];
[native release];
[rep release];
if (cursor->ns.object == nil)
return GLFW_FALSE;

View File

@ -1007,3 +1007,5 @@ void* _glfw_calloc(size_t count, size_t size);
void* _glfw_realloc(void* pointer, size_t size);
void _glfw_free(void* pointer);
const GLFWimage* _glfwChooseImage(int count, const GLFWimage* images,
int width, int height);

View File

@ -75,28 +75,6 @@ static DWORD getWindowExStyle(const _GLFWwindow* window)
return style;
}
// Returns the image whose area most closely matches the desired one
//
static const GLFWimage* chooseImage(int count, const GLFWimage* images,
int width, int height)
{
int i, leastDiff = INT_MAX;
const GLFWimage* closest = NULL;
for (i = 0; i < count; i++)
{
const int currDiff = abs(images[i].width * images[i].height -
width * height);
if (currDiff < leastDiff)
{
closest = images + i;
leastDiff = currDiff;
}
}
return closest;
}
// Creates an RGBA icon or cursor
//
static HICON createIcon(const GLFWimage* image, int xhot, int yhot, GLFWbool icon)
@ -1545,10 +1523,10 @@ void _glfwSetWindowIconWin32(_GLFWwindow* window, int count, const GLFWimage* im
if (count)
{
const GLFWimage* bigImage = chooseImage(count, images,
const GLFWimage* bigImage = _glfwChooseImage(count, images,
GetSystemMetrics(SM_CXICON),
GetSystemMetrics(SM_CYICON));
const GLFWimage* smallImage = chooseImage(count, images,
const GLFWimage* smallImage = _glfwChooseImage(count, images,
GetSystemMetrics(SM_CXSMICON),
GetSystemMetrics(SM_CYSMICON));

View File

@ -175,6 +175,28 @@ void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor)
window->monitor = monitor;
}
// Returns the image whose area most closely matches the desired one
//
const GLFWimage* _glfwChooseImage(int count, const GLFWimage* images,
int width, int height)
{
int i, leastDiff = INT_MAX;
const GLFWimage* closest = NULL;
for (i = 0; i < count; i++)
{
const int currDiff = abs(images[i].width * images[i].height -
width * height);
if (currDiff < leastDiff)
{
closest = images + i;
leastDiff = currDiff;
}
}
return closest;
}
//////////////////////////////////////////////////////////////////////////
////// GLFW public API //////
//////////////////////////////////////////////////////////////////////////
@ -531,7 +553,6 @@ GLFWAPI void glfwSetWindowIcon(GLFWwindow* handle,
int i;
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
assert(count >= 0);
assert(count == 0 || images != NULL);

View File

@ -29,6 +29,7 @@ add_executable(monitors monitors.c ${GETOPT} ${GLAD_GL})
add_executable(reopen reopen.c ${GLAD_GL})
add_executable(cursor cursor.c ${GLAD_GL})
add_executable(animate-icon WIN32 MACOSX_BUNDLE animate-icon.c ${GLAD_GL})
add_executable(empty WIN32 MACOSX_BUNDLE empty.c ${TINYCTHREAD} ${GLAD_GL})
add_executable(gamma WIN32 MACOSX_BUNDLE gamma.c ${GLAD_GL})
add_executable(icon WIN32 MACOSX_BUNDLE icon.c ${GLAD_GL})
@ -48,7 +49,7 @@ if (RT_LIBRARY)
target_link_libraries(threads "${RT_LIBRARY}")
endif()
set(GUI_ONLY_BINARIES empty gamma icon inputlag joysticks tearing threads
set(GUI_ONLY_BINARIES animate-icon empty gamma icon inputlag joysticks tearing threads
timeout title triangle-vulkan window)
set(CONSOLE_BINARIES allocator clipboard events msaa glfwinfo iconify monitors
reopen cursor)

202
tests/animate-icon.c Normal file
View File

@ -0,0 +1,202 @@
//========================================================================
// Window icon animation test program
// Copyright (c) Camilla Löwy <elmindreda@glfw.org>
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would
// be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not
// be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source
// distribution.
//
//========================================================================
//
// This program is used to test the icon feature.
//
//========================================================================
#define GLAD_GL_IMPLEMENTATION
#include <glad/gl.h>
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <assert.h>
#define ALPHA 220
#define WIDTH 3
#define HEIGHT 3
#define SPEED 0.3
const unsigned char iconColors[5][3] =
{
{ 1, 0, 0 }, // Red
{ 0, 1, 0 }, // Green
{ 0, 0, 1 }, // Blue
{ 1, 1, 1 } // All
};
static int currentIconColor = -1;
static int animate = GLFW_FALSE;
static int smooth = GLFW_FALSE;
static int useOptimalSize = GLFW_FALSE;
static int width = WIDTH, height = HEIGHT;
static void error_callback(int error_code, const char* description)
{
fprintf(stderr, "Error %i: %s\n", error_code, description);
}
static void set_icon(GLFWwindow* window, int iconColor)
{
int x, y;
double time = glfwGetTime();
double red = (cos(SPEED * time * M_PI ) + 1.0) / 2.0;
double green = (cos(SPEED * time * M_PI * 1.5) + 1.0) / 2.0;
double blue = (cos(SPEED * time * M_PI * 2.0) + 1.0) / 2.0;
unsigned char pixels[width * height * 4];
for (x = 0; x < width; ++x)
{
for (y = 0; y < height; ++y)
{
int offset = x * height * 4 + y * 4;
pixels[offset + 0] = iconColors[currentIconColor][0] * (char) (255.0 * red);
pixels[offset + 1] = iconColors[currentIconColor][1] * (char) (255.0 * green);
pixels[offset + 2] = iconColors[currentIconColor][2] * (char) (255.0 * blue);
pixels[offset + 3] = (char) (255.0 * ((double) (x + y) / (double) (width + height)));
}
}
GLFWimage image = { width, height, pixels };
glfwSetErrorCallback(NULL);
glfwSetWindowIcon(window, 1, &image);
glfwSetWindowIcon(0, 1, &image);
glfwSetErrorCallback(error_callback);
}
static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
if (action != GLFW_PRESS)
return;
switch (key)
{
case GLFW_KEY_ESCAPE:
glfwSetWindowShouldClose(window, GLFW_TRUE);
break;
case GLFW_KEY_SPACE:
currentIconColor = (currentIconColor + 1) % 4;
animate = GLFW_TRUE;
break;
case GLFW_KEY_S:
smooth = !smooth;
break;
case GLFW_KEY_O:
{
GLFWimage image = { GLFW_DONT_CARE, GLFW_DONT_CARE, NULL };
glfwSetWindowIcon(NULL, 0, &image);
printf("Optimal size: [%i, %i]\n", image.width, image.height);
useOptimalSize = !useOptimalSize;
break;
}
case GLFW_KEY_X:
glfwSetWindowIcon(window, 0, NULL);
glfwSetWindowIcon(0, 0, NULL);
currentIconColor = -1;
animate = GLFW_FALSE;
break;
}
}
int main(int argc, char** argv)
{
GLFWwindow* window;
glfwSetErrorCallback(error_callback);
if (!glfwInit())
{
fprintf(stderr, "Failed to initialize GLFW\n");
exit(EXIT_FAILURE);
}
window = glfwCreateWindow(200, 200, "Window Icon", NULL, NULL);
if (!window)
{
glfwTerminate();
fprintf(stderr, "Failed to open GLFW window\n");
exit(EXIT_FAILURE);
}
glfwMakeContextCurrent(window);
gladLoadGL(glfwGetProcAddress);
glfwSwapInterval(60);
glfwSetKeyCallback(window, key_callback);
double lastTime = 0.0;
while (!glfwWindowShouldClose(window))
{
if (useOptimalSize)
{
GLFWimage image = { GLFW_DONT_CARE, GLFW_DONT_CARE, NULL };
glfwSetWindowIcon(NULL, 0, &image);
if (image.width != GLFW_DONT_CARE)
{
assert(image.height != GLFW_DONT_CARE);
width = image.width;
height = image.height;
}
}
else
{
width = WIDTH;
height = HEIGHT;
}
double time = glfwGetTime();
if (animate && (time - lastTime) > (smooth ? 0.01 : 0.15))
{
set_icon(window, currentIconColor);
lastTime = time;
}
glClear(GL_COLOR_BUFFER_BIT);
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwDestroyWindow(window);
glfwTerminate();
exit(EXIT_SUCCESS);
}
// TODO: rollback all changes in this branch. Create a new competing branch to set-application-icon, add this file's content as a new test program (animateIcon?)
// Do not add glfwSetApplicationIcon in the new one. Rather, use glfwSetWindowIcon with NULL handle, and add support for getting an optimal max size.

View File

@ -89,6 +89,7 @@ static void set_icon(GLFWwindow* window, int icon_color)
}
glfwSetWindowIcon(window, 1, &img);
glfwSetWindowIcon(NULL, 1, &img);
}
static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
@ -107,6 +108,7 @@ static void key_callback(GLFWwindow* window, int key, int scancode, int action,
break;
case GLFW_KEY_X:
glfwSetWindowIcon(window, 0, NULL);
glfwSetWindowIcon(NULL, 0, NULL);
break;
}
}