From 4de19165c004dbd961bc935b164c55dd4af585f0 Mon Sep 17 00:00:00 2001 From: Orson Peters Date: Wed, 12 Feb 2014 13:44:12 +0100 Subject: [PATCH] Added window icon API and Win32 implementation. --- README.md | 2 + include/GLFW/glfw3.h | 18 ++++++ src/cocoa_window.m | 5 ++ src/internal.h | 5 ++ src/win32_window.c | 142 ++++++++++++++++++++++++++++++++++++++++ src/window.c | 13 ++++ src/x11_window.c | 5 ++ tests/CMakeLists.txt | 6 +- tests/icons.c | 151 +++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 346 insertions(+), 1 deletion(-) create mode 100644 tests/icons.c diff --git a/README.md b/README.md index 174bbe832..166dcc361 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ GLFW bundles a number of dependencies in the `deps/` directory. ## Changelog + - Added `glfwSetWindowIcons` for setting window icons - Changed minimum required CMake version to 2.8.12 - Bugfix: Initialization failed on headless systems - Bugfix: The cached current context could get out of sync @@ -171,6 +172,7 @@ skills. - Arturo J. Pérez - Emmanuel Gil Peyrot - Cyril Pichard + - Orson Peters - Pieroman - Jorge Rodriguez - Ed Ropple diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index 9a7ca1764..8d2303224 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -1916,6 +1916,24 @@ GLFWAPI void glfwGetFramebufferSize(GLFWwindow* window, int* width, int* height) */ GLFWAPI void glfwGetWindowFrameSize(GLFWwindow* window, int* left, int* top, int* right, int* bottom); +/*! @brief Sets the application icons to use for the given window. + * @param[in] window The window to set the icons for. + * @param[in] icons A pointer to the first element of an array of GLFWimage structs. + * @param[in] numicons The number of icons in the given array. + * @ingroup window + * + * @note This function may only be called from the main thread. + * + * @note From all the given icons GLFW will automatically pick the most appropriate + * size for the different locations in which the application icon can occur. For + * example on Windows, if a larger and a smaller icon are given the larger icon + * will be used for the ALT-TAB screen and the smaller for the taskbar. + * + * @note If the icon does not exactly fit the operating systems requirements for the + * icon size the icon will be automatically resized. + */ +GLFWAPI void glfwSetWindowIcons(GLFWwindow* window, GLFWimage* icons, int numicons); + /*! @brief Iconifies the specified window. * * This function iconifies (minimizes) the specified window if it was diff --git a/src/cocoa_window.m b/src/cocoa_window.m index fbd73bc8f..19bc93c2c 100644 --- a/src/cocoa_window.m +++ b/src/cocoa_window.m @@ -1042,6 +1042,11 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, *bottom = contentRect.origin.y - frameRect.origin.y; } +void _glfwPlatformSetWindowIcons(_GLFWwindow* window, GLFWimage *icons, int numicons) +{ + /* TODO: implement this */ +} + void _glfwPlatformIconifyWindow(_GLFWwindow* window) { [window->ns.object miniaturize:nil]; diff --git a/src/internal.h b/src/internal.h index c2fac3c5b..2b1d3df73 100644 --- a/src/internal.h +++ b/src/internal.h @@ -539,6 +539,11 @@ void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* heigh */ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, int* left, int* top, int* right, int* bottom); +/*! @copydoc glfwSetWindowIcons + * @ingroup platform + */ +void _glfwPlatformSetWindowIcons(_GLFWwindow* window, GLFWimage* icons, int numicons); + /*! @copydoc glfwIconifyWindow * @ingroup platform */ diff --git a/src/win32_window.c b/src/win32_window.c index d120ebf07..337f87b7d 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -727,6 +727,136 @@ static void destroyWindow(_GLFWwindow* window) } } +// Creates a Windows icon from a GLFWimage +// +static HICON createIcon(GLFWimage* image) +{ + BITMAPV5HEADER header; + HDC hdc; + unsigned char* BGRAData; + HBITMAP bitmap, mask; + ICONINFO iconinfo; + HICON icon; + unsigned char* dibData; + int i; + + // fill in BITMAPV5HEADER to pass to CreateDIBSection + header.bV5Size = sizeof(header); + header.bV5Width = image->width; + header.bV5Height = image->height; + header.bV5Planes = 1; + header.bV5BitCount = 32; + header.bV5Compression = BI_BITFIELDS; + header.bV5RedMask = 0x00ff0000; + header.bV5GreenMask = 0x0000ff00; + header.bV5BlueMask = 0x000000ff; + header.bV5AlphaMask = 0xff000000; + + // create a HBITMAP that we can write to + hdc = GetDC(NULL); + bitmap = CreateDIBSection(hdc, (BITMAPINFO*) &header, DIB_RGB_COLORS, (void**) &dibData, NULL, 0); + ReleaseDC(NULL, hdc); + + // first we need to convert RGBA to BGRA (yay Windows!) + // we also need to convert lines, because Windows wants bottom-to-top RGBA + BGRAData = malloc(image->width * image->height * 4); + if (!BGRAData) + { + _glfwInputError(GLFW_OUT_OF_MEMORY, NULL); + return NULL; + } + + for (i = 0; i < image->width * image->height; i++) { + unsigned char *dst = BGRAData + 4 * i; + unsigned char *src = image->pixels + 4 * i; + + dst[0] = src[2]; // copy blue channel + dst[1] = src[1]; // copy green channel + dst[2] = src[0]; // copy red channel + dst[3] = src[3]; // copy alpha channel + } + + // copy the BGRA data into dibData + memcpy(dibData, BGRAData, image->width * image->height * 4); + + // free the BGRA data + free(BGRAData); + + // create a mask that we don't use (but needed for iconinfo) + mask = CreateBitmap(image->width, image->height, 1, 1, NULL); + + iconinfo.fIcon = TRUE; + iconinfo.hbmMask = mask; + iconinfo.hbmColor = bitmap; + + // create our icon + icon = CreateIconIndirect(&iconinfo); + + // clean up + DeleteObject(mask); + DeleteObject(bitmap); + + return icon; +} + +// Chooses the best fitting image given the images and desired size +// +static GLFWimage* bestFit(GLFWimage *icons, const int numicons, const int targetWidth, const int targetHeight) +{ + GLFWimage *curIcon = icons; + GLFWimage *bestIcon = curIcon; + const double targetRatio = (double) targetWidth / targetHeight; + + while (curIcon < icons + numicons) + { + // always use exact match + if (curIcon->width == targetWidth && curIcon->height == targetHeight) + { + return curIcon; + } + + // at least wide or high enough, ratio preferably as close as possible + if (curIcon->width >= targetWidth || curIcon->height >= targetHeight) + { + const double curRatio = (double) curIcon->width / curIcon->height; + const double bestRatio = (double) bestIcon->width / bestIcon->height; + double curDelta = targetRatio - curRatio; + double bestDelta = targetRatio - bestRatio; + + if (curDelta < 0) + { + curDelta = -curDelta; + } + + if (bestDelta < 0) + { + bestDelta = -bestDelta; + } + + // if our ratio is closer OR if the best icon so far isn't large + // enough we'll become the new best icon + if ((curDelta < bestDelta) + || (bestIcon->width < targetWidth && bestIcon->height < targetHeight)) + { + bestIcon = curIcon; + } + } + + // maybe nothing is wide or high enough, if that's the case + // we'll get the largest thing available (in area) + else if (bestIcon->width < targetWidth && bestIcon->height < targetHeight) + { + if (curIcon->width * curIcon->height > bestIcon->width * bestIcon->height) + { + bestIcon = curIcon; + } + } + + ++curIcon; + } + + return bestIcon; +} ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// @@ -929,6 +1059,18 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, *bottom = rect.bottom - height; } +void _glfwPlatformSetWindowIcons(_GLFWwindow* window, GLFWimage *icons, int numicons) +{ + GLFWimage* normalicon; + GLFWimage* smallicon; + + normalicon = bestFit(icons, numicons, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON)); + smallicon = bestFit(icons, numicons, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON)); + + SendMessage(window->win32.handle, WM_SETICON, ICON_BIG, (LPARAM) createIcon(normalicon)); + SendMessage(window->win32.handle, WM_SETICON, ICON_SMALL, (LPARAM) createIcon(smallicon)); +} + void _glfwPlatformIconifyWindow(_GLFWwindow* window) { ShowWindow(window->win32.handle, SW_MINIMIZE); diff --git a/src/window.c b/src/window.c index 506bd49ba..3bb4e5359 100644 --- a/src/window.c +++ b/src/window.c @@ -513,6 +513,19 @@ GLFWAPI void glfwGetWindowFrameSize(GLFWwindow* handle, _glfwPlatformGetWindowFrameSize(window, left, top, right, bottom); } +GLFWAPI void glfwSetWindowIcons(GLFWwindow* handle, GLFWimage* icons, int numicons) +{ + _GLFWwindow* window = (_GLFWwindow*) handle; + _GLFW_REQUIRE_INIT(); + + if (numicons < 1) + { + return; + } + + _glfwPlatformSetWindowIcons(window, icons, numicons); +} + GLFWAPI void glfwIconifyWindow(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; diff --git a/src/x11_window.c b/src/x11_window.c index 33e517082..e25dc0af2 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -1645,6 +1645,11 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, XFree(extents); } +void _glfwPlatformSetWindowIcons(_GLFWwindow* window, GLFWimage *icons, int numicons) +{ + /* TODO: implement this */ +} + void _glfwPlatformIconifyWindow(_GLFWwindow* window) { if (window->x11.overrideRedirect) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1f8acc748..a1a73bf23 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -42,6 +42,9 @@ set_target_properties(accuracy PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Accuracy") add_executable(empty WIN32 MACOSX_BUNDLE empty.c ${TINYCTHREAD}) set_target_properties(empty PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Empty Event") +add_executable(icons WIN32 MACOSX_BUNDLE icons.c) +set_target_properties(icons PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Icons") + add_executable(sharing WIN32 MACOSX_BUNDLE sharing.c) set_target_properties(sharing PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Sharing") @@ -60,7 +63,8 @@ set_target_properties(windows PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Windows") target_link_libraries(empty "${CMAKE_THREAD_LIBS_INIT}" "${RT_LIBRARY}") target_link_libraries(threads "${CMAKE_THREAD_LIBS_INIT}" "${RT_LIBRARY}") -set(WINDOWS_BINARIES accuracy empty sharing tearing threads title windows cursoranim) +set(WINDOWS_BINARIES accuracy empty icons sharing tearing threads title windows + cursoranim) set(CONSOLE_BINARIES clipboard defaults events msaa gamma glfwinfo iconify joysticks monitors peter reopen cursor) diff --git a/tests/icons.c b/tests/icons.c new file mode 100644 index 000000000..819efa129 --- /dev/null +++ b/tests/icons.c @@ -0,0 +1,151 @@ +//======================================================================== +// Window icon test program +// Copyright (c) Camilla Berglund +// +// 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. +// +//======================================================================== + +#include + +#include +#include +#include + +// a simple glfw logo +const char * const logo[] = { + "................", + "................", + "...0000..0......", + "...0.....0......", + "...0.00..0......", + "...0..0..0......", + "...0000..0000...", + "................", + "................", + "...000..0...0...", + "...0....0...0...", + "...000..0.0.0...", + "...0....0.0.0...", + "...0....00000...", + "................", + "................" +}; + +const unsigned char icon_colors[5][4] = { + {0x00, 0x00, 0x00, 0xff}, // black + {0xff, 0x00, 0x00, 0xff}, // red + {0x00, 0xff, 0x00, 0xff}, // green + {0xff, 0x00, 0xff, 0xff}, // blue + {0xff, 0xff, 0xff, 0xff} // white +}; + +static int cur_icon_color = 0; + +static void set_icon(GLFWwindow* window, int icon_color) { + GLFWimage img; + int x, y; + char* pixels; + + // create image + img.width = 16; + img.height = 16; + pixels = malloc(img.width * img.height * 4); + + if (!pixels) + { + glfwTerminate(); + + fprintf(stderr, "Failed to allocate memory.\n"); + exit(EXIT_FAILURE); + } + + for (x = 0; x < 16; ++x) + { + for (y = 0; y < 16; ++y) + { + // 15 - y because we need to flip the icon + if (logo[15 - y][x] == '0') + memcpy(pixels + 4 * (x + y * img.width), icon_colors[icon_color], 4); + else + memset(pixels + 4 * (x + y * img.width), 0, 4); // transparency + } + } + + img.pixels = pixels; + + glfwSetWindowIcons(window, &img, 1); +} + +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: + glfwDestroyWindow(window); + break; + case GLFW_KEY_SPACE: + cur_icon_color = (cur_icon_color + 1) % 5; + set_icon(window, cur_icon_color); + break; + } +} + + +int main(int argc, char** argv) +{ + GLFWwindow* window; + + if (!glfwInit()) + { + fprintf(stderr, "Failed to initialize GLFW\n"); + exit(EXIT_FAILURE); + } + + window = glfwCreateWindow(800, 600, "Icons", NULL, NULL); + if (!window) + { + glfwTerminate(); + + fprintf(stderr, "Failed to open GLFW window\n"); + exit(EXIT_FAILURE); + } + + glfwSetKeyCallback(window, key_callback); + set_icon(window, cur_icon_color); + + while (!glfwWindowShouldClose(window)) + { + glfwSwapBuffers(window); + glfwPollEvents(); + } + + glfwTerminate(); + + return EXIT_SUCCESS; +}