This commit is contained in:
Andreas 2024-01-30 19:33:36 -08:00 committed by GitHub
commit 39b47a3029
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 378 additions and 70 deletions

View File

@ -104,6 +104,7 @@ video tutorials.
- IntellectualKitty
- Aaron Jacobs
- JannikGM
- Andreas O. Jansen
- Erik S. V. Jansson
- jjYBdx4IL
- Peter Johnson

View File

@ -122,6 +122,7 @@ information on what to include when reporting a bug.
## Changelog since 3.3.9
- 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

@ -906,6 +906,22 @@ To revert to the default window icon, pass in an empty image array.
glfwSetWindowIcon(window, 0, NULL);
@endcode
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 for 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
@subsection window_monitor Window monitor

View File

@ -3266,12 +3266,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
@ -3279,9 +3279,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
@ -3294,13 +3302,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

@ -191,6 +191,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 };
@ -1031,8 +1060,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)
@ -1714,35 +1779,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

@ -1004,3 +1004,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

@ -29,7 +29,6 @@
#if defined(_GLFW_WIN32)
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <windowsx.h>
@ -73,28 +72,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)
@ -1565,15 +1542,23 @@ void _glfwSetWindowTitleWin32(_GLFWwindow* window, const char* title)
void _glfwSetWindowIconWin32(_GLFWwindow* window, int count, const GLFWimage* images)
{
HICON bigIcon = NULL, smallIcon = NULL;
if (window == NULL)
{
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
"Win32: Requires a valid window handle to set the icon. There is no application icon to set.");
return;
}
if (count)
{
const GLFWimage* bigImage = chooseImage(count, images,
GetSystemMetrics(SM_CXICON),
GetSystemMetrics(SM_CYICON));
const GLFWimage* smallImage = chooseImage(count, images,
GetSystemMetrics(SM_CXSMICON),
GetSystemMetrics(SM_CYSMICON));
const GLFWimage* bigImage = _glfwChooseImage(count, images,
GetSystemMetrics(SM_CXICON),
GetSystemMetrics(SM_CYICON));
const GLFWimage* smallImage = _glfwChooseImage(count, images,
GetSystemMetrics(SM_CXSMICON),
GetSystemMetrics(SM_CYSMICON));
bigIcon = createIcon(bigImage, 0, 0, GLFW_TRUE);
smallIcon = createIcon(smallImage, 0, 0, GLFW_TRUE);

View File

@ -28,6 +28,7 @@
#include "internal.h"
#include <limits.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
@ -173,6 +174,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 //////
//////////////////////////////////////////////////////////////////////////
@ -529,7 +552,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

@ -2124,7 +2124,7 @@ void _glfwSetWindowIconWayland(_GLFWwindow* window,
int count, const GLFWimage* images)
{
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
"Wayland: The platform does not support setting the window icon");
"Wayland: The platform does not support setting the window or application icon");
}
void _glfwGetWindowPosWayland(_GLFWwindow* window, int* xpos, int* ypos)

View File

@ -2102,6 +2102,14 @@ void _glfwSetWindowTitleX11(_GLFWwindow* window, const char* title)
void _glfwSetWindowIconX11(_GLFWwindow* window, int count, const GLFWimage* images)
{
if (window == NULL)
{
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
"X11: Requires a valid window handle to set the icon. There is no application icon to set.");
return;
}
if (count)
{
int longCount = 0;

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)

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

@ -0,0 +1,208 @@
//========================================================================
// 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>
#ifndef M_PI
#define M_PI 3.14159265358979323846264338327950288
#endif
#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)
{
GLFWimage image;
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 = malloc(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)));
}
}
image.width = width;
image.height = height;
image.pixels = pixels;
glfwSetErrorCallback(NULL);
glfwSetWindowIcon(window, 1, &image);
glfwSetWindowIcon(0, 1, &image);
glfwSetErrorCallback(error_callback);
free(pixels);
}
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;
double lastTime = 0.0;
double time;
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);
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;
}
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);
}

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