From 40c04a7565bafb962b83cd85c9103684bb9bcb47 Mon Sep 17 00:00:00 2001 From: urraka Date: Wed, 4 Dec 2013 10:19:22 -0300 Subject: [PATCH] Added support for custom system cursors. This adds 3 functions to the GLFW API: glfwCreateCursor, glfwDestroyCursor and glfwSetCursor. --- CMakeLists.txt | 23 ++-- include/GLFW/glfw3.h | 52 ++++++++ src/cocoa_platform.h | 11 ++ src/cocoa_window.m | 64 +++++++++- src/init.c | 4 + src/input.c | 75 ++++++++++++ src/internal.h | 21 ++++ src/win32_platform.h | 10 ++ src/win32_window.c | 95 ++++++++++++++- src/x11_platform.h | 11 ++ src/x11_window.c | 58 ++++++++- tests/CMakeLists.txt | 8 +- tests/cursor.c | 283 +++++++++++++++++++++++++++++++++++++++++++ tests/cursoranim.c | 133 ++++++++++++++++++++ 14 files changed, 836 insertions(+), 12 deletions(-) create mode 100644 tests/cursor.c create mode 100644 tests/cursoranim.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 22a3ef36..a9ab1aa7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,7 +121,7 @@ endif() #-------------------------------------------------------------------- if (WIN32) set(_GLFW_WIN32 1) - message(STATUS "Using Win32 for window creation") + message(STATUS "Using Win32 for window creation") if (GLFW_USE_EGL) set(_GLFW_EGL 1) @@ -137,7 +137,7 @@ elseif (APPLE) message(STATUS "Using NSGL for context creation") elseif (UNIX) set(_GLFW_X11 1) - message(STATUS "Using X11 for window creation") + message(STATUS "Using X11 for window creation") if (GLFW_USE_EGL) set(_GLFW_EGL 1) @@ -250,7 +250,7 @@ if (_GLFW_X11) # Check for Xkb (X keyboard extension) if (NOT X11_Xkb_FOUND) message(FATAL_ERROR "The X keyboard extension headers were not found") - endif() + endif() list(APPEND glfw_INCLUDE_DIR ${X11_Xkb_INCLUDE_PATH}) @@ -268,6 +268,15 @@ if (_GLFW_X11) set(GLFW_PKG_LIBS "${GLFW_PKG_LIBS} -lm") endif() + # Check for Xcursor + if (NOT X11_Xcursor_FOUND) + message(FATAL_ERROR "The Xcursor libraries and headers were not found") + endif() + + list(APPEND glfw_INCLUDE_DIR ${X11_Xcursor_INCLUDE_PATH}) + list(APPEND glfw_LIBRARIES ${X11_Xcursor_LIB}) + set(GLFW_PKG_DEPS "${GLFW_PKG_DEPS} xcursor") + endif() #-------------------------------------------------------------------- @@ -338,7 +347,7 @@ endif() # Use Cocoa for window creation and NSOpenGL for context creation #-------------------------------------------------------------------- if (_GLFW_COCOA AND _GLFW_NSGL) - + if (GLFW_USE_MENUBAR) set(_GLFW_USE_MENUBAR 1) endif() @@ -357,7 +366,7 @@ if (_GLFW_COCOA AND _GLFW_NSGL) else() message(STATUS "Building GLFW only for the native architecture") endif() - + # Set up library and include paths find_library(COCOA_FRAMEWORK Cocoa) find_library(IOKIT_FRAMEWORK IOKit) @@ -394,7 +403,7 @@ endif() configure_file(${GLFW_SOURCE_DIR}/docs/Doxyfile.in ${GLFW_BINARY_DIR}/docs/Doxyfile @ONLY) -configure_file(${GLFW_SOURCE_DIR}/src/glfw_config.h.in +configure_file(${GLFW_SOURCE_DIR}/src/glfw_config.h.in ${GLFW_BINARY_DIR}/src/glfw_config.h @ONLY) configure_file(${GLFW_SOURCE_DIR}/src/glfwConfig.cmake.in @@ -428,7 +437,7 @@ endif() # The library is installed by src/CMakeLists.txt #-------------------------------------------------------------------- if (GLFW_INSTALL) - install(DIRECTORY include/GLFW DESTINATION include + install(DIRECTORY include/GLFW DESTINATION include FILES_MATCHING PATTERN glfw3.h PATTERN glfw3native.h) install(FILES ${GLFW_BINARY_DIR}/src/glfwConfig.cmake diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index 4d6756cb..a3334095 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -575,6 +575,14 @@ typedef struct GLFWmonitor GLFWmonitor; */ typedef struct GLFWwindow GLFWwindow; +/*! @brief Opaque cursor object. + * + * Opaque cursor object. + * + * @ingroup cursor + */ +typedef struct GLFWcursor GLFWcursor; + /*! @brief The function signature for error callbacks. * * This is the function signature for error callback functions. @@ -1926,6 +1934,50 @@ GLFWAPI void glfwGetCursorPos(GLFWwindow* window, double* xpos, double* ypos); */ GLFWAPI void glfwSetCursorPos(GLFWwindow* window, double xpos, double ypos); +/*! @brief Creates a cursor. + * + * @param[in] width The desired cursor width. + * @param[in] height The desired cursor height. + * @param[in] xhot The desired x-coordinate of the cursor hotspot. + * @param[in] yhot The desired y-coordinate of the cursor hotspot. + * @param[in] format Not used. + * @param[in] data The cursor image data in RGBA8 format, packed in rows from + * top to bottom. + * + * @return A new cursor ready to use or `NULL` if an error occurred. If you + * don't destroy the cursor by calling `glfwDestroyCursor` it will be destroyed + * automatically by `GLFW` on termination. + * + * @note This function may only be called from the main thread. + * + * @ingroup input + */ +GLFWAPI GLFWcursor* glfwCreateCursor(int width, int height, int xhot, int yhot, int format, const void* data); + +/*! @brief Destroys a cursor. + * + * This function destroys a cursor previously created by a call to + * `glfwCreateCursor`. `GLFW` will destroy all cursors automatically on + * termination. + * + * @param[in] cursor The cursor to destroy. + * + * @note This function may only be called from the main thread. + * + * @ingroup input + */ +GLFWAPI void glfwDestroyCursor(GLFWcursor* cursor); + +/*! @brief Sets the cursor for a given window. + * + * @param[in] window The window to set the cursor for. + * @param[in] cursor The cursor to change to, or `NULL` to switch back to the + * default system cursor. + * + * @ingroup input + */ +GLFWAPI void glfwSetCursor(GLFWwindow* window, GLFWcursor* cursor); + /*! @brief Sets the key callback. * * This function sets the key callback of the specific window, which is called diff --git a/src/cocoa_platform.h b/src/cocoa_platform.h index ea512e49..b69bb69a 100644 --- a/src/cocoa_platform.h +++ b/src/cocoa_platform.h @@ -51,6 +51,7 @@ typedef void* id; #define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowNS ns #define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryNS ns #define _GLFW_PLATFORM_MONITOR_STATE _GLFWmonitorNS ns +#define _GLFW_PLATFORM_CURSOR_STATE _GLFWcursorNS ns //======================================================================== @@ -67,6 +68,7 @@ typedef struct _GLFWwindowNS id delegate; id view; unsigned int modifierFlags; + int cursorInside; } _GLFWwindowNS; @@ -123,6 +125,15 @@ typedef struct _GLFWmonitorNS } _GLFWmonitorNS; +//------------------------------------------------------------------------ +// Platform-specific cursor structure +//------------------------------------------------------------------------ +typedef struct _GLFWcursorNS +{ + id handle; +} _GLFWcursorNS; + + //======================================================================== // Prototypes for platform specific internal functions //======================================================================== diff --git a/src/cocoa_window.m b/src/cocoa_window.m index 6b05baca..b4ac29f2 100644 --- a/src/cocoa_window.m +++ b/src/cocoa_window.m @@ -44,7 +44,12 @@ static void centerCursor(_GLFWwindow *window) static void setModeCursor(_GLFWwindow* window) { if (window->cursorMode == GLFW_CURSOR_NORMAL) - [[NSCursor arrowCursor] set]; + { + if (window->cursor) + [(NSCursor*) window->cursor->ns.handle set]; + else + [[NSCursor arrowCursor] set]; + } else [(NSCursor*) _glfw.ns.cursor set]; } @@ -556,11 +561,13 @@ static int translateKey(unsigned int key) - (void)mouseExited:(NSEvent *)event { + window->ns.cursorInside = GL_FALSE; _glfwInputCursorEnter(window, GL_FALSE); } - (void)mouseEntered:(NSEvent *)event { + window->ns.cursorInside = GL_TRUE; _glfwInputCursorEnter(window, GL_TRUE); } @@ -1194,6 +1201,61 @@ void _glfwPlatformApplyCursorMode(_GLFWwindow* window) CGAssociateMouseAndMouseCursorPosition(true); } +int _glfwPlatformCreateCursor(_GLFWcursor* cursor, int width, int height, int cx, int cy, + int format, const void* data) +{ + NSImage* image; + NSBitmapImageRep* rep; + + rep = [[NSBitmapImageRep alloc] + initWithBitmapDataPlanes:NULL + pixelsWide:width + pixelsHigh:height + bitsPerSample:8 + samplesPerPixel:4 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSCalibratedRGBColorSpace + bitmapFormat:NSAlphaNonpremultipliedBitmapFormat + bytesPerRow:width * 4 + bitsPerPixel:32]; + + if (rep == nil) + return GL_FALSE; + + memcpy([rep bitmapData], data, 4 * width * height); + + image = [[NSImage alloc] initWithSize:NSMakeSize(width, height)]; + [image addRepresentation: rep]; + + cursor->ns.handle = [[NSCursor alloc] initWithImage:image + hotSpot:NSMakePoint(cx, cy)]; + + [image release]; + [rep release]; + + if (cursor->ns.handle == nil) + return GL_FALSE; + + return GL_TRUE; +} + +void _glfwPlatformDestroyCursor(_GLFWcursor* cursor) +{ + [(NSCursor*) cursor->ns.handle release]; +} + +void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor) +{ + if (window->cursorMode == GLFW_CURSOR_NORMAL && window->ns.cursorInside) + { + if (cursor) + [(NSCursor*) cursor->ns.handle set]; + else + [[NSCursor arrowCursor] set]; + } +} + ////////////////////////////////////////////////////////////////////////// ////// GLFW native API ////// diff --git a/src/init.c b/src/init.c index 4541462d..d7a8b445 100644 --- a/src/init.c +++ b/src/init.c @@ -156,6 +156,10 @@ GLFWAPI void glfwTerminate(void) while (_glfw.windowListHead) glfwDestroyWindow((GLFWwindow*) _glfw.windowListHead); + // Destroy all cursors + while (_glfw.cursorListHead) + glfwDestroyCursor((GLFWcursor*) _glfw.cursorListHead); + for (i = 0; i < _glfw.monitorCount; i++) { _GLFWmonitor* monitor = _glfw.monitors[i]; diff --git a/src/input.c b/src/input.c index 493f2446..477cf030 100644 --- a/src/input.c +++ b/src/input.c @@ -27,6 +27,11 @@ #include "internal.h" +#include +#if defined(_MSC_VER) + #include +#endif + // Internal key state used for sticky keys #define _GLFW_STICK 3 @@ -348,6 +353,76 @@ GLFWAPI void glfwSetCursorPos(GLFWwindow* handle, double xpos, double ypos) _glfwPlatformSetCursorPos(window, xpos, ypos); } +GLFWAPI GLFWcursor* glfwCreateCursor(int width, int height, int cx, int cy, + int format, const void* data) +{ + _GLFWcursor* cursor; + + _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + + cursor = calloc(1, sizeof(_GLFWcursor)); + + if (!_glfwPlatformCreateCursor(cursor, width, height, cx, cy, format, data)) + { + free(cursor); + return NULL; + } + + cursor->next = _glfw.cursorListHead; + _glfw.cursorListHead = cursor; + + return (GLFWcursor*) cursor; +} + +GLFWAPI void glfwDestroyCursor(GLFWcursor* handle) +{ + _GLFWcursor* cursor = (_GLFWcursor*) handle; + + _GLFW_REQUIRE_INIT(); + + if (cursor == NULL) + return; + + // Make sure the cursor is not being used by any window + { + _GLFWwindow* window = _glfw.windowListHead; + + while (window) + { + if (window->cursor == cursor) + glfwSetCursor((GLFWwindow*) window, NULL); + + window = window->next; + } + } + + _glfwPlatformDestroyCursor(cursor); + + // Unlink cursor from global linked list + { + _GLFWcursor** prev = &_glfw.cursorListHead; + + while (*prev != cursor) + prev = &((*prev)->next); + + *prev = cursor->next; + } + + free(cursor); +} + +GLFWAPI void glfwSetCursor(GLFWwindow* windowHandle, GLFWcursor* cursorHandle) +{ + _GLFWwindow* window = (_GLFWwindow*) windowHandle; + _GLFWcursor* cursor = (_GLFWcursor*) cursorHandle; + + _GLFW_REQUIRE_INIT(); + + _glfwPlatformSetCursor(window, cursor); + + window->cursor = cursor; +} + GLFWAPI GLFWkeyfun glfwSetKeyCallback(GLFWwindow* handle, GLFWkeyfun cbfun) { _GLFWwindow* window = (_GLFWwindow*) handle; diff --git a/src/internal.h b/src/internal.h index 8c4d11ea..e9b70206 100644 --- a/src/internal.h +++ b/src/internal.h @@ -65,6 +65,7 @@ typedef struct _GLFWfbconfig _GLFWfbconfig; typedef struct _GLFWwindow _GLFWwindow; typedef struct _GLFWlibrary _GLFWlibrary; typedef struct _GLFWmonitor _GLFWmonitor; +typedef struct _GLFWcursor _GLFWcursor; #if defined(_GLFW_COCOA) #include "cocoa_platform.h" @@ -218,6 +219,7 @@ struct _GLFWwindow void* userPointer; GLFWvidmode videoMode; _GLFWmonitor* monitor; + _GLFWcursor* cursor; // Window input state GLboolean stickyKeys; @@ -285,6 +287,17 @@ struct _GLFWmonitor }; +/*! @brief Cursor structure + */ + +struct _GLFWcursor +{ + _GLFWcursor* next; + + // This is defined in the window API's platform.h + _GLFW_PLATFORM_CURSOR_STATE; +}; + /*! @brief Library global data. */ struct _GLFWlibrary @@ -319,6 +332,8 @@ struct _GLFWlibrary double cursorPosX, cursorPosY; + _GLFWcursor* cursorListHead; + _GLFWwindow* windowListHead; _GLFWwindow* focusedWindow; @@ -573,6 +588,12 @@ int _glfwPlatformExtensionSupported(const char* extension); */ GLFWglproc _glfwPlatformGetProcAddress(const char* procname); +int _glfwPlatformCreateCursor(_GLFWcursor* cursor, int width, int height, int cx, int cy, + int format, const void* data); + +void _glfwPlatformDestroyCursor(_GLFWcursor* cursor); + +void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor); //======================================================================== // Event API functions diff --git a/src/win32_platform.h b/src/win32_platform.h index 66b92f04..5ecdd09f 100644 --- a/src/win32_platform.h +++ b/src/win32_platform.h @@ -164,6 +164,7 @@ typedef HRESULT (WINAPI * DWMISCOMPOSITIONENABLED_T)(BOOL*); #define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowWin32 win32 #define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryWin32 win32 #define _GLFW_PLATFORM_MONITOR_STATE _GLFWmonitorWin32 win32 +#define _GLFW_PLATFORM_CURSOR_STATE _GLFWcursorWin32 win32 //======================================================================== @@ -250,6 +251,15 @@ typedef struct _GLFWmonitorWin32 } _GLFWmonitorWin32; +//------------------------------------------------------------------------ +// Platform-specific cursor structure +//------------------------------------------------------------------------ +typedef struct _GLFWcursorWin32 +{ + HCURSOR handle; +} _GLFWcursorWin32; + + //======================================================================== // Prototypes for platform specific internal functions //======================================================================== diff --git a/src/win32_window.c b/src/win32_window.c index b6085381..d9271f2f 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -100,7 +100,12 @@ static void restoreCursor(_GLFWwindow* window) if (GetCursorPos(&pos)) { if (WindowFromPoint(pos) == window->win32.handle) - SetCursor(LoadCursorW(NULL, IDC_ARROW)); + { + if (window->cursor) + SetCursor(window->cursor->win32.handle); + else + SetCursor(LoadCursorW(NULL, IDC_ARROW)); + } } } @@ -720,6 +725,11 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, SetCursor(NULL); return TRUE; } + else if (window->cursor) + { + SetCursor(window->cursor->win32.handle); + return TRUE; + } } break; @@ -1213,6 +1223,89 @@ void _glfwPlatformApplyCursorMode(_GLFWwindow* window) } } +int _glfwPlatformCreateCursor(_GLFWcursor* cursor, int width, int height, int cx, int cy, + int format, const void* data) +{ + HDC hdc; + HBITMAP hBitmap, hMonoBitmap; + BITMAPV5HEADER bi; + ICONINFO ii; + DWORD *buffer = 0; + BYTE *image = (BYTE*) data; + int i, size = width * height; + + ZeroMemory(&bi, sizeof(BITMAPV5HEADER)); + + bi.bV5Size = sizeof(BITMAPV5HEADER); + bi.bV5Width = width; + bi.bV5Height = -height; + bi.bV5Planes = 1; + bi.bV5BitCount = 32; + bi.bV5Compression = BI_BITFIELDS; + bi.bV5RedMask = 0x00FF0000; + bi.bV5GreenMask = 0x0000FF00; + bi.bV5BlueMask = 0x000000FF; + bi.bV5AlphaMask = 0xFF000000; + + hdc = GetDC(NULL); + + hBitmap = CreateDIBSection(hdc, (BITMAPINFO*) &bi, DIB_RGB_COLORS, (void**) &buffer, + NULL, (DWORD) 0); + + ReleaseDC(NULL, hdc); + + if (hBitmap == NULL) + return GL_FALSE; + + hMonoBitmap = CreateBitmap(width, height, 1, 1, NULL); + + if (hMonoBitmap == NULL) + { + DeleteObject(hBitmap); + return GL_FALSE; + } + + for (i = 0; i < size; i++, buffer++, image += 4) + *buffer = (image[3] << 24) | (image[0] << 16) | (image[1] << 8) | image[2]; + + ii.fIcon = FALSE; + ii.xHotspot = cx; + ii.yHotspot = cy; + ii.hbmMask = hMonoBitmap; + ii.hbmColor = hBitmap; + + cursor->win32.handle = (HCURSOR) CreateIconIndirect(&ii); + + DeleteObject(hBitmap); + DeleteObject(hMonoBitmap); + + if (cursor->win32.handle == NULL) + return GL_FALSE; + + return GL_TRUE; +} + +void _glfwPlatformDestroyCursor(_GLFWcursor* cursor) +{ + DestroyIcon((HICON) cursor->win32.handle); +} + +void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor) +{ + // It should be guaranteed that the cursor is not being used by this window if + // the following condition is not met. That way it should be safe to destroy the + // cursor after calling glfwSetCursor(window, NULL) on all windows using the cursor. + + if (window->cursorMode == GLFW_CURSOR_NORMAL && _glfw.focusedWindow == window && + window->win32.cursorInside) + { + if (cursor) + SetCursor(cursor->win32.handle); + else + SetCursor(LoadCursor(NULL, IDC_ARROW)); + } +} + ////////////////////////////////////////////////////////////////////////// ////// GLFW native API ////// diff --git a/src/x11_platform.h b/src/x11_platform.h index e4846538..b76a1393 100644 --- a/src/x11_platform.h +++ b/src/x11_platform.h @@ -34,6 +34,7 @@ #include #include #include +#include // The Xf86VidMode extension provides fallback gamma control #include @@ -62,6 +63,7 @@ #define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowX11 x11 #define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryX11 x11 #define _GLFW_PLATFORM_MONITOR_STATE _GLFWmonitorX11 x11 +#define _GLFW_PLATFORM_CURSOR_STATE _GLFWcursorX11 x11 //======================================================================== @@ -232,6 +234,15 @@ typedef struct _GLFWmonitorX11 } _GLFWmonitorX11; +//------------------------------------------------------------------------ +// Platform-specific cursor structure +//------------------------------------------------------------------------ +typedef struct _GLFWcursorX11 +{ + Cursor handle; +} _GLFWcursorX11; + + //======================================================================== // Prototypes for platform specific internal functions //======================================================================== diff --git a/src/x11_window.c b/src/x11_window.c index d642a302..d4324c19 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -394,7 +394,14 @@ static void disableCursor(_GLFWwindow* window) static void restoreCursor(_GLFWwindow* window) { XUngrabPointer(_glfw.x11.display, CurrentTime); - XUndefineCursor(_glfw.x11.display, window->x11.handle); + + if (window->cursor) + { + XDefineCursor(_glfw.x11.display, window->x11.handle, + window->cursor->x11.handle); + } + else + XUndefineCursor(_glfw.x11.display, window->x11.handle); } // Enter fullscreen mode @@ -1349,6 +1356,55 @@ void _glfwPlatformApplyCursorMode(_GLFWwindow* window) } } +int _glfwPlatformCreateCursor(_GLFWcursor* cursor, int width, int height, int cx, int cy, + int format, const void* data) +{ + XcursorImage* cursorImage; + XcursorPixel* buffer; + unsigned char* image = (unsigned char*) data; + int i, size = width * height; + + cursorImage = XcursorImageCreate(width, height); + + if (cursorImage == NULL) + return GL_FALSE; + + cursorImage->xhot = cx; + cursorImage->yhot = cy; + + buffer = cursorImage->pixels; + + for (i = 0; i < size; i++, buffer++, image += 4) + *buffer = (image[3] << 24) | (image[0] << 16) | (image[1] << 8) | image[2]; + + cursor->x11.handle = XcursorImageLoadCursor(_glfw.x11.display, cursorImage); + + XcursorImageDestroy(cursorImage); + + if (cursor->x11.handle == None) + return GL_FALSE; + + return GL_TRUE; +} + +void _glfwPlatformDestroyCursor(_GLFWcursor* cursor) +{ + XFreeCursor(_glfw.x11.display, cursor->x11.handle); +} + +void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor) +{ + if (window->cursorMode == GLFW_CURSOR_NORMAL) + { + if (cursor) + XDefineCursor(_glfw.x11.display, window->x11.handle, cursor->x11.handle); + else + XUndefineCursor(_glfw.x11.display, window->x11.handle); + + XFlush(_glfw.x11.display); + } +} + ////////////////////////////////////////////////////////////////////////// ////// GLFW native API ////// diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 456cf136..b7646f98 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -32,6 +32,10 @@ add_executable(joysticks joysticks.c) add_executable(modes modes.c ${GETOPT}) add_executable(peter peter.c) add_executable(reopen reopen.c) +add_executable(cursor cursor.c) + +add_executable(cursoranim WIN32 MACOSX_BUNDLE cursoranim.c) +set_target_properties(cursoranim PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Cursor animation") add_executable(accuracy WIN32 MACOSX_BUNDLE accuracy.c) set_target_properties(accuracy PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Accuracy") @@ -57,9 +61,9 @@ 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) +set(WINDOWS_BINARIES accuracy empty sharing tearing threads title windows cursoranim) set(CONSOLE_BINARIES clipboard defaults events fsaa gamma glfwinfo - iconify joysticks modes peter reopen) + iconify joysticks modes peter reopen cursor) if (MSVC) # Tell MSVC to use main instead of WinMain for Windows subsystem executables diff --git a/tests/cursor.c b/tests/cursor.c new file mode 100644 index 00000000..4e673871 --- /dev/null +++ b/tests/cursor.c @@ -0,0 +1,283 @@ +//======================================================================== +// Cursor & input mode tests +// 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. +// +//======================================================================== +// +// System cursors and input modes tests. +// +//======================================================================== + +#define GLFW_INCLUDE_GLU +#include + +#include +#include + +static int W = 640; +static int H = 480; +static int delay = 0; + +static GLFWwindow* windows[2] = {NULL, NULL}; +static GLFWwindow* activeWindow = NULL; +static GLFWcursor* cursor = NULL; + +static struct { int key; double time; } commands[] = { + {GLFW_KEY_H, 0}, + {GLFW_KEY_C, 0}, + {GLFW_KEY_D, 0}, + {GLFW_KEY_S, 0}, + {GLFW_KEY_N, 0}, + {GLFW_KEY_1, 0}, + {GLFW_KEY_2, 0}, + {GLFW_KEY_3, 0} +}; + +static int CommandCount = sizeof(commands) / sizeof(commands[0]); + +static struct { int w, h; } cursorSize[] = { + {24, 24}, {13, 37}, {5, 53}, {43, 64}, {300, 300} +}; + +static int SizeCount = sizeof(cursorSize) / sizeof(cursorSize[0]); +static int currentSize = 0; + +static void command_callback(int key) +{ + switch (key) + { + case GLFW_KEY_H: + { + printf("H: show this help\n"); + printf("C: call glfwCreateCursor()\n"); + printf("D: call glfwDestroyCursor()\n"); + printf("S: call glfwSetCursor()\n"); + printf("N: call glfwSetCursor() with NULL\n"); + printf("1: set GLFW_CURSOR_NORMAL\n"); + printf("2: set GLFW_CURSOR_HIDDEN\n"); + printf("3: set GLFW_CURSOR_DISABLED\n"); + printf("T: enable 3s delay for all previous commands\n"); + } + break; + + case GLFW_KEY_C: + { + if (cursor == NULL) + { + int w = cursorSize[currentSize].w; + int h = cursorSize[currentSize].h; + int x, y, i = 0; + unsigned char *image = malloc(4 * w * h); + + for (y = 0; y < h; y++) + { + for (x = 0; x < w; x++) + { + image[i++] = 0xFF; + image[i++] = 0; + image[i++] = 255 * y / h; + image[i++] = 255 * x / w; + } + } + + cursor = glfwCreateCursor(w, h, w / 2, h / 2, 0, image); + currentSize = (currentSize + 1) % SizeCount; + free(image); + } + } + break; + + case GLFW_KEY_D: + { + if (cursor != NULL) + { + glfwDestroyCursor(cursor); + cursor = NULL; + } + } + break; + + case GLFW_KEY_S: + { + if (cursor != NULL) + glfwSetCursor(activeWindow, cursor); + else + printf("The cursor is not created\n"); + } + break; + + case GLFW_KEY_N: + { + glfwSetCursor(activeWindow, NULL); + } + break; + + case GLFW_KEY_1: + { + glfwSetInputMode(activeWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + } + break; + + case GLFW_KEY_2: + { + glfwSetInputMode(activeWindow, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); + } + break; + + case GLFW_KEY_3: + { + glfwSetInputMode(activeWindow, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + } + break; + } +} + +static void error_callback(int error, const char* description) +{ + fprintf(stderr, "Error: %s\n", description); +} + +static void framebuffer_size_callback(GLFWwindow* window, int width, int height) +{ + W = width; + H = height; + + glViewport(0, 0, W, H); +} + +static void refresh_callback(GLFWwindow* window) +{ + glfwMakeContextCurrent(window); + glClearColor(0.0f, window == activeWindow ? 0.8f : 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + glfwSwapBuffers(window); +} + +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, GL_TRUE); + break; + + case GLFW_KEY_T: + delay = !delay; + printf("Delay %s.\n", delay ? "enabled" : "disabled"); + break; + + default: + { + if (delay) + { + int i = 0; + + while (i < CommandCount && commands[i].key != key) + i++; + + if (i < CommandCount) + commands[i].time = glfwGetTime(); + } + else + { + command_callback(key); + } + } + break; + } +} + +static void focus_callback(GLFWwindow* window, int focused) +{ + if (focused) + { + activeWindow = window; + refresh_callback(windows[0]); + refresh_callback(windows[1]); + } +} + +int main(void) +{ + int i; + GLboolean running = GL_TRUE; + + glfwSetErrorCallback(error_callback); + + if (!glfwInit()) + exit(EXIT_FAILURE); + + for (i = 0; i < 2; i++) + { + windows[i] = glfwCreateWindow(W, H, "Cursor testing", NULL, NULL); + + if (!windows[i]) + { + glfwTerminate(); + exit(EXIT_FAILURE); + } + + glfwSetWindowPos(windows[i], 100 + (i & 1) * (W + 50), 100); + + glfwSetWindowRefreshCallback(windows[i], refresh_callback); + glfwSetFramebufferSizeCallback(windows[i], framebuffer_size_callback); + glfwSetKeyCallback(windows[i], key_callback); + glfwSetWindowFocusCallback(windows[i], focus_callback); + + glfwMakeContextCurrent(windows[i]); + glClear(GL_COLOR_BUFFER_BIT); + glfwSwapBuffers(windows[i]); + } + + activeWindow = windows[0]; + + key_callback(NULL, GLFW_KEY_H, 0, GLFW_PRESS, 0); + + while (running) + { + if (delay) + { + int i; + double t = glfwGetTime(); + + for (i = 0; i < CommandCount; i++) + { + if (commands[i].time != 0 && t - commands[i].time >= 3.0) + { + command_callback(commands[i].key); + commands[i].time = 0; + } + } + } + + running = !(glfwWindowShouldClose(windows[0]) || glfwWindowShouldClose(windows[1])); + + glfwPollEvents(); + } + + glfwTerminate(); + exit(EXIT_SUCCESS); +} + diff --git a/tests/cursoranim.c b/tests/cursoranim.c new file mode 100644 index 00000000..b7c78600 --- /dev/null +++ b/tests/cursoranim.c @@ -0,0 +1,133 @@ +//======================================================================== +// Cursor animation +// 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. +// +//======================================================================== +// +// Cursor animation test. +// +//======================================================================== + +#include + +#include +#include + +#ifdef min + #undef min +#endif +#ifdef max + #undef max +#endif + +#define SIZE 64 // cursor size (width & height) +#define N 60 // number of frames + +unsigned char buffer[4 * SIZE * SIZE]; + +static float max(float a, float b) { return a > b ? a : b; } +static float min(float a, float b) { return a < b ? a : b; } + +static float star(int x, int y, float t) +{ + float c = SIZE / 2.0f; + + float i = (0.25f * (float)sin(2.0f * 3.1415926f * t) + 0.75f); + float k = SIZE * 0.046875f * i; + + float dist = (float)sqrt((x - c) * (x - c) + (y - c) * (y - c)); + + float salpha = 1.0f - dist / c; + float xalpha = (float)x == c ? c : k / (float)fabs(x - c); + float yalpha = (float)y == c ? c : k / (float)fabs(y - c); + + return max(0.0f, min(1.0f, i * salpha * 0.2f + salpha * xalpha * yalpha)); +} + +static GLFWcursor* load_frame(float t) +{ + int i = 0, x, y; + + for (y = 0; y < SIZE; y++) + { + for (x = 0; x < SIZE; x++) + { + buffer[i++] = 255; + buffer[i++] = 255; + buffer[i++] = 255; + buffer[i++] = (unsigned char)(255 * star(x, y, t)); + } + } + + return glfwCreateCursor(SIZE, SIZE, SIZE / 2, SIZE / 2, 0, buffer); +} + +int main(void) +{ + int i; + double t0, t1, frameTime = 0.0; + + GLFWwindow* window; + GLFWcursor* frames[N]; + + if (!glfwInit()) + exit(EXIT_FAILURE); + + window = glfwCreateWindow(640, 480, "Cursor animation", NULL, NULL); + + if (!window) + { + glfwTerminate(); + exit(EXIT_FAILURE); + } + + glfwMakeContextCurrent(window); + glfwSwapInterval(1); + + for (i = 0; i < N; i++) + frames[i] = load_frame(i / (float)N); + + i = 0; + + t0 = glfwGetTime(); + + while (!glfwWindowShouldClose(window)) + { + glClear(GL_COLOR_BUFFER_BIT); + glfwSetCursor(window, frames[i]); + glfwSwapBuffers(window); + glfwPollEvents(); + + t1 = glfwGetTime(); + frameTime += t1 - t0; + t0 = t1; + + while (frameTime > 1.0 / (double)N) + { + i = (i + 1) % N; + frameTime -= 1.0 / (double)N; + } + } + + glfwTerminate(); + exit(EXIT_SUCCESS); +}