diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 75533ec3..4bef331d 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -33,6 +33,7 @@ video tutorials. - David Carlier - Arturo Castro - Chi-kwan Chan + - Haoyun Chen - TheChocolateOre - Ali Chraghi - Joseph Chua @@ -103,6 +104,7 @@ video tutorials. - IntellectualKitty - Aaron Jacobs - JannikGM + - Andreas O. Jansen - Erik S. V. Jansson - jjYBdx4IL - Peter Johnson diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index eb98dafd..029d2299 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -1404,6 +1404,18 @@ typedef struct GLFWwindow GLFWwindow; */ typedef struct GLFWcursor GLFWcursor; +/*! @brief Opaque theme object. + * + * Opaque theme object. + * + * @see @ref theme_object + * + * @since Added in version 3.4. + * + * @ingroup theme + */ +typedef struct GLFWtheme GLFWtheme; + /*! @brief The function pointer type for memory allocation callbacks. * * This is the function pointer type for memory allocation callbacks. A memory @@ -1974,6 +1986,28 @@ typedef void (* GLFWmonitorfun)(GLFWmonitor* monitor, int event); */ typedef void (* GLFWjoystickfun)(int jid, int event); +/*! @brief The function pointer type for system theme callbacks. + * + * This is the function pointer type for system theme callbacks. A system + * theme callback function has the following signature: + * @code + * void function_name(GLFWtheme* theme) + * @endcode + * + * @param[in] theme The new system theme. + * + * @pointer_lifetime The theme is valid until the callback + * function returns. + * + * @sa @ref theme + * @sa @ref glfwSetSystemThemeCallback + * + * @since Added in version 3.4. + * + * @ingroup theme + */ +typedef void (* GLFWthemefun)(GLFWtheme* theme); + /*! @brief Video mode type. * * This describes a single video mode. @@ -5858,6 +5892,209 @@ GLFWAPI void glfwSetClipboardString(GLFWwindow* window, const char* string); */ GLFWAPI const char* glfwGetClipboardString(GLFWwindow* window); +// TODO: consider requiring GLFW to be initialized for these GLFWTheme functions to work. That way, implementations can add extra data to the themes, and process them in a platform-specific way. + +GLFWAPI GLFWtheme* glfwCreateTheme(void); +GLFWAPI void glfwDestroyTheme(GLFWtheme* theme); +GLFWAPI void glfwCopyTheme(const GLFWtheme* source, GLFWtheme* target); +GLFWAPI int glfwThemeEqual(const GLFWtheme* first, const GLFWtheme* second); + +GLFWAPI int glfwThemeGetVariation(const GLFWtheme* theme); +GLFWAPI void glfwThemeSetVariation(GLFWtheme* theme, int value); + +GLFWAPI int glfwThemeGetAttribute(const GLFWtheme* theme, int attribute); +GLFWAPI void glfwThemeSetAttribute(GLFWtheme* theme, int attribute, int value); + +// If the return value of glfwGetAttribute(specifier) is GLFW_FALSE, the return values of this function are undefined. +GLFWAPI void glfwThemeGetColor(const GLFWtheme* theme, + int specifier, + float* red, float* green, float* blue, float* alpha); + +// Must execute glfwThemeSetAttribute(specifier, GLFW_TRUE) for this to have an effect. +GLFWAPI void glfwThemeSetColor(GLFWtheme* theme, + int specifier, + float red, float green, float blue, float alpha); + +/*! @brief Theme variation type. + * + * Specifies that a theme is of a dark variation. + * + * @ingroup theme + */ +#define GLFW_THEME_DARK -1 + +/*! @brief Theme variation type. + * + * Specifies that a theme has no specific variation. + * This is an invalid value for themes returned by GLFW, but this + * variation can be set to make GLFW use the default system theme when + * applying a user-specified theme. + * + * @ingroup theme + */ +#define GLFW_THEME_DEFAULT 0 + +/*! @brief Theme variation type. + * + * Specifies that a theme is of a light variation. + * + * @ingroup theme + */ +#define GLFW_THEME_LIGHT 1 + +/*! @brief Theme attribute. + * + * Specifies that a theme uses a high contrast mode. + * + * @ingroup theme + */ +#define GLFW_THEME_ATTRIBUTE_HIGH_CONTRAST 1 + +/*! @brief Theme attribute. + * + * Specifies that a theme requests reduced transparency. + * + * @ingroup theme + */ +#define GLFW_THEME_ATTRIBUTE_REDUCE_TRANSPARENCY 2 + +/*! @brief Theme attribute. + * + * Specifies that a theme requests reduced motion. + * + * @ingroup theme + */ +#define GLFW_THEME_ATTRIBUTE_REDUCE_MOTION 4 + +/*! @brief Theme color attribute. + * + * This is both an attribute and color specifier. As an attribute, it specifies + * if this color is present in the theme or not. If this attribute is set on a theme + * returned by GLFW, that theme contains this color. If this attribute is set on a + * theme supplied to GLFW, GLFW applies this color if the platform supports it. + * + * @ingroup theme + */ +#define GLFW_THEME_COLOR_MAIN 8 + +/*! @brief Sets the system theme callback. + * + * This function sets the system theme callback, or removes the + * currently set callback. This is called when the system theme changes, + * either because the user edited it, or the system automatically changed it. + * + * @param[in] callback The new callback, or `NULL` to remove the currently set + * callback. + * @return The previously set callback, or `NULL` if no callback was set or the + * library had not been [initialized](@ref intro_init). + * + * @callback_signature + * @code + * void function_name(GLFWtheme* theme) + * @endcode + * For more information about the callback parameters, see the + * [function pointer type](@ref GLFWthemefun). + * + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. + * + * @thread_safety This function must only be called from the main thread. + * + * @sa @ref theme_event + * + * @since Added in version 3.4. + * + * @ingroup theme + */ +GLFWAPI GLFWthemefun glfwSetSystemThemeCallback(GLFWthemefun callback); + +/*! @brief Sets the theme for a window. + * + * @param[in] window The [window](@ref window) to set the theme for. + * @param[in] theme The [theme](@ref theme) to set. Pass `NULL` to set it to the system default. + * + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, + * and @ref GLFW_FEATURE_UNIMPLEMENTED. + * + * @pointer_lifetime The returned theme is allocated and freed by GLFW. You + * should not free it yourself. It is valid until this function or @ref glfwGetTheme + * is called again for the specified window, or the window is destroyed. + * + * @thread_safety This function must only be called from the main thread. + * + * @sa @ref theme + * @sa @ref glfwGetTheme + * + * @since Added in version 3.4. + * + * @ingroup theme + */ +GLFWAPI void glfwSetTheme(GLFWwindow* handle, const GLFWtheme* theme); + +/*! @brief Returns the currently active system theme. + * + * Executing this yields no changes to a window's theme settings: + * @code + * glfwSetTheme(window, glfwGetTheme(window, GLFW_FALSE)) + * @endcode + * + * Executing this sets a window's theme to the current system theme, and disables + * automatic changes to the window's theme for when the system changes its theme. + * @code + * glfwSetTheme(window, glfwGetTheme(window, GLFW_TRUE)) + * @endcode + * + * + * @param[in] window The [window](@ref window) to retrieve the current theme for. + * @param[in] inlineDefaults Specifies whether or not GLFW should replace unspecified + * theme attributes with the currently active system ones. If `GLFW_TRUE`, the returned + * theme describes the active style of the window. If `GLFW_FALSE`, the returned theme + * describes the window's theme settings. + * + * @return A mutable [theme](@ref theme) object, or `NULL` if an + * [error](@ref error_handling) occurred. + * + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, + * and @ref GLFW_FEATURE_UNIMPLEMENTED. + * + * @pointer_lifetime The returned theme is allocated and freed by GLFW. You + * should not free it yourself. It is valid until this function or @ref glfwSetTheme + * is called again for the specified window, or the window is destroyed. + * + * @thread_safety This function must only be called from the main thread. + * + * @sa @ref theme + * @sa @ref glfwSetTheme + * + * @since Added in version 3.4. + * + * @ingroup theme + */ +GLFWAPI GLFWtheme* glfwGetTheme(GLFWwindow* handle, int inlineDefaults); + +/*! @brief Returns the currently active system theme. + * + * @return A mutable [theme](@ref theme) object, or `NULL` if an + * [error](@ref error_handling) occurred. + * + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, + * and @ref GLFW_FEATURE_UNIMPLEMENTED. + * + * @pointer_lifetime The returned theme is allocated and freed by GLFW. You + * should not free it yourself. It is valid until this function is called + * again, or the library is terminated. + * + * @thread_safety This function must only be called from the main thread. + * + * @sa @ref theme + * @sa @ref glfwSetSystemThemeCallback + * @sa @ref glfwSetTheme + * + * @since Added in version 3.4. + * + * @ingroup theme + */ +GLFWAPI const GLFWtheme* glfwGetSystemDefaultTheme(); + /*! @brief Returns the GLFW time. * * This function returns the current GLFW time, in seconds. Unless the time diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7b84ecf2..6cb9be3a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,7 +3,7 @@ add_library(glfw ${GLFW_LIBRARY_TYPE} "${GLFW_SOURCE_DIR}/include/GLFW/glfw3.h" "${GLFW_SOURCE_DIR}/include/GLFW/glfw3native.h" internal.h platform.h mappings.h - context.c init.c input.c monitor.c platform.c vulkan.c window.c + context.c init.c input.c monitor.c platform.c theme.c vulkan.c window.c egl_context.c osmesa_context.c null_platform.h null_joystick.h null_init.c null_monitor.c null_window.c null_joystick.c) @@ -249,7 +249,7 @@ endif() # Make GCC warn about declarations that VS 2010 and 2012 won't accept for all # source files that VS will build (Clang ignores this because we set -std=c99) if (CMAKE_C_COMPILER_ID STREQUAL "GNU") - set_source_files_properties(context.c init.c input.c monitor.c platform.c vulkan.c + set_source_files_properties(context.c init.c input.c monitor.c platform.c theme.c vulkan.c window.c null_init.c null_joystick.c null_monitor.c null_window.c win32_init.c win32_joystick.c win32_module.c win32_monitor.c win32_time.c win32_thread.c win32_window.c diff --git a/src/cocoa_init.m b/src/cocoa_init.m index b3831df1..a80d6a44 100644 --- a/src/cocoa_init.m +++ b/src/cocoa_init.m @@ -175,6 +175,119 @@ static void createMenuBar(void) [NSApp performSelector:setAppleMenuSelector withObject:appMenu]; } +void _glfwNSAppearanceToTheme(NSAppearance* appearance, _GLFWtheme* theme) +{ + NSAppearanceName name; + + if (@available(macOS 10.14, *)) + { + name = [appearance bestMatchFromAppearancesWithNames:@[ + NSAppearanceNameAqua, + NSAppearanceNameDarkAqua, + NSAppearanceNameVibrantLight, + NSAppearanceNameVibrantDark, + NSAppearanceNameAccessibilityHighContrastAqua, + NSAppearanceNameAccessibilityHighContrastDarkAqua, + ]]; + } else + { + name = appearance.name; + } + + if ([name isEqualToString:NSAppearanceNameAqua]) + { + theme->variation = GLFW_THEME_LIGHT; + return; + } + + if (@available(macOS 10.10, *)) { + if ([name isEqualToString:NSAppearanceNameVibrantLight]) + { + theme->variation = GLFW_THEME_LIGHT; + return; + } + if ([name isEqualToString:NSAppearanceNameVibrantDark]) + { + theme->variation = GLFW_THEME_DARK; + return; + } + } + + if (@available(macOS 10.14, *)) + { + if ([name isEqualToString:NSAppearanceNameDarkAqua]) + { + theme->variation = GLFW_THEME_DARK; + return; + } + if ([name isEqualToString:NSAppearanceNameAccessibilityHighContrastAqua]) + { + theme->variation = GLFW_THEME_LIGHT; + theme->flags |= GLFW_THEME_ATTRIBUTE_HIGH_CONTRAST; + return; + } + if ([name isEqualToString:NSAppearanceNameAccessibilityHighContrastDarkAqua]) + { + theme->variation = GLFW_THEME_DARK; + theme->flags |= GLFW_THEME_ATTRIBUTE_HIGH_CONTRAST; + return; + } + } + + theme->variation = GLFW_THEME_LIGHT; +} + +void _glfwGetSystemThemeCocoa(_GLFWtheme* theme) +{ + theme->variation = GLFW_THEME_LIGHT; + theme->flags = 0; + + if (@available(macOS 10.14, *)) + { + // effectiveAppearance is actually not the system appearance, but the application appearance. + // As long as NSApplication.appearance is never set, using the effective appearance is fine + // to get and observe the system appearance. + _glfwNSAppearanceToTheme(NSApp.effectiveAppearance, theme); + + NSColor* color = [[NSColor controlAccentColor] colorUsingColorSpace:NSColorSpace.genericRGBColorSpace]; + + theme->flags |= GLFW_THEME_COLOR_MAIN; + theme->color[0] = color.redComponent; + theme->color[1] = color.greenComponent; + theme->color[2] = color.blueComponent; + theme->color[3] = color.alphaComponent; + } + + // TODO: return the standard blue accent color if running in 10.13 or earlier. + + // TODO: optimize by reading multiple values at once. + + const CFStringRef applicationID = CFSTR("com.apple.universalaccess"); + + Boolean keyIsValid = false; + Boolean highContrast = CFPreferencesGetAppBooleanValue(CFSTR("increaseContrast"), + applicationID, + &keyIsValid); + + if (keyIsValid && highContrast) + theme->flags |= GLFW_THEME_ATTRIBUTE_HIGH_CONTRAST; + + keyIsValid = false; + Boolean reduceTransparency = CFPreferencesGetAppBooleanValue(CFSTR("reduceTransparency"), + applicationID, + &keyIsValid); + if (keyIsValid && reduceTransparency) + theme->flags |= GLFW_THEME_ATTRIBUTE_REDUCE_TRANSPARENCY; + + keyIsValid = false; + Boolean reduceMotion = CFPreferencesGetAppBooleanValue(CFSTR("reduceMotion"), + applicationID, + &keyIsValid); + + if (keyIsValid && reduceMotion) + theme->flags |= GLFW_THEME_ATTRIBUTE_REDUCE_MOTION; +} + // Create key code translation tables // static void createKeyTables(void) @@ -393,6 +506,42 @@ static GLFWbool initializeTIS(void) - (void)doNothing:(id)object { } +/* +- (void)themeChanged:(NSNotification*)notification +{ + _glfwInputSystemTheme(NULL); +} + +- (void)accentColorChanged +{ + _glfwInputSystemTheme(NULL); +}*/ + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101400 +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context +{ + // This class is never subclassed, so it's safe to ignore the context parameter + + _GLFWtheme theme; + _glfwGetSystemThemeCocoa(&theme); + + // The observer for the effective appearance is invoked more often than the appearance name itself changes. + // For instance, it's invoked when various accesibility settings are changed in the system preferences. + // Not all of these properties are included in the GLFW themes, so those updates must be filtered out. + if (glfwThemeEqual((GLFWtheme*) &theme, (GLFWtheme*) &_glfw.theme)) + return; + + memcpy(&_glfw.theme, &theme, sizeof(_GLFWtheme)); + _glfwInputSystemTheme(&theme); + + /*if ([keyPath isEqualToString:@"controlAccentColor"]) { + [self accentColorChanged]; + }*/ +} +#endif @end // GLFWHelper @@ -496,8 +645,11 @@ GLFWbool _glfwConnectCocoa(int platformID, _GLFWplatform* platform) const _GLFWplatform cocoa = { GLFW_PLATFORM_COCOA, + // Init _glfwInitCocoa, _glfwTerminateCocoa, + _glfwGetSystemDefaultThemeCocoa, + // Input _glfwGetCursorPosCocoa, _glfwSetCursorPosCocoa, _glfwSetCursorModeCocoa, @@ -516,6 +668,7 @@ GLFWbool _glfwConnectCocoa(int platformID, _GLFWplatform* platform) _glfwPollJoystickCocoa, _glfwGetMappingNameCocoa, _glfwUpdateGamepadGUIDCocoa, + // Monitor _glfwFreeMonitorCocoa, _glfwGetMonitorPosCocoa, _glfwGetMonitorContentScaleCocoa, @@ -524,6 +677,7 @@ GLFWbool _glfwConnectCocoa(int platformID, _GLFWplatform* platform) _glfwGetVideoModeCocoa, _glfwGetGammaRampCocoa, _glfwSetGammaRampCocoa, + // Window _glfwCreateWindowCocoa, _glfwDestroyWindowCocoa, _glfwSetWindowTitleCocoa, @@ -557,13 +711,18 @@ GLFWbool _glfwConnectCocoa(int platformID, _GLFWplatform* platform) _glfwSetWindowFloatingCocoa, _glfwSetWindowOpacityCocoa, _glfwSetWindowMousePassthroughCocoa, + _glfwGetThemeCocoa, + _glfwSetThemeCocoa, + // Events _glfwPollEventsCocoa, _glfwWaitEventsCocoa, _glfwWaitEventsTimeoutCocoa, _glfwPostEmptyEventCocoa, + // EGL _glfwGetEGLPlatformCocoa, _glfwGetEGLNativeDisplayCocoa, _glfwGetEGLNativeWindowCocoa, + // Vulkan _glfwGetRequiredInstanceExtensionsCocoa, _glfwGetPhysicalDevicePresentationSupportCocoa, _glfwCreateWindowSurfaceCocoa, @@ -632,6 +791,30 @@ int _glfwInitCocoa(void) return GLFW_FALSE; _glfwPollMonitorsCocoa(); + + /* + [[NSNotificationCenter defaultCenter] + addObserver:_glfw.ns.helper + selector:@selector(themeChanged:) + name:@"AppleInterfaceThemeChangedNotification" + object:nil]; + + [NSColor addObserver:_glfw.ns.helper + forKeyPath:@"controlAccentColor" + options:0 + context:nil]; + */ + + _glfwInitDefaultTheme(&_glfw.theme); + + // TODO: add observer for properties other than effectiveAppearance, for observing prior to 10.14. I assume the accesibility options were available then. + if (@available(macOS 10.14, *)) + { + [NSApp addObserver:_glfw.ns.helper + forKeyPath:@"effectiveAppearance" + options:0 + context:nil]; + } if (![[NSRunningApplication currentApplication] isFinishedLaunching]) [NSApp run]; @@ -693,5 +876,13 @@ void _glfwTerminateCocoa(void) } // autoreleasepool } +_GLFWtheme* _glfwGetSystemDefaultThemeCocoa(void) +{ + _GLFWtheme* theme = &_glfw.theme; + _glfwGetSystemThemeCocoa(theme); + + return theme; +} + #endif // _GLFW_COCOA diff --git a/src/cocoa_platform.h b/src/cocoa_platform.h index 9f7d191d..18a4ea02 100644 --- a/src/cocoa_platform.h +++ b/src/cocoa_platform.h @@ -39,6 +39,12 @@ #import #else typedef void* id; +typedef void NSAppearance; +typedef id NSString; +#endif + +#if MAC_OS_X_VERSION_MIN_REQUIRED < 101300 +typedef NSString *NSAppearanceName; #endif // NOTE: Many Cocoa enum values have been renamed and we need to build across @@ -291,6 +297,9 @@ void _glfwRestoreVideoModeCocoa(_GLFWmonitor* monitor); float _glfwTransformYCocoa(float y); +void _glfwNSAppearanceToTheme(NSAppearance* appearance, _GLFWtheme* theme); +void _glfwGetSystemThemeCocoa(_GLFWtheme* theme); + void* _glfwLoadLocalVulkanLoaderCocoa(void); GLFWbool _glfwInitNSGL(void); @@ -300,3 +309,6 @@ GLFWbool _glfwCreateContextNSGL(_GLFWwindow* window, const _GLFWfbconfig* fbconfig); void _glfwDestroyContextNSGL(_GLFWwindow* window); +_GLFWtheme* _glfwGetSystemDefaultThemeCocoa(void); +void _glfwSetThemeCocoa(_GLFWwindow* window, const _GLFWtheme* theme); +_GLFWtheme* _glfwGetThemeCocoa(_GLFWwindow* window, int inlineDefaults); diff --git a/src/cocoa_window.m b/src/cocoa_window.m index 9d794940..c6968411 100644 --- a/src/cocoa_window.m +++ b/src/cocoa_window.m @@ -1881,6 +1881,119 @@ const char* _glfwGetClipboardStringCocoa(void) } // autoreleasepool } +// TODO: move +static void replaceTheme(_GLFWwindow* window, _GLFWtheme* currentTheme, const _GLFWtheme* newTheme) +{ + const int dissimilarFlags = currentTheme->flags ^ newTheme->flags; + + if (currentTheme->variation != newTheme->variation + || (dissimilarFlags & GLFW_THEME_ATTRIBUTE_REDUCE_TRANSPARENCY)) + { + if (newTheme->variation == GLFW_THEME_DEFAULT) + { + [(NSWindow*)window->ns.object setAppearance:nil]; + } + else + { + if (@available(macOS 10.10, *)) + { + // TODO: support color + // TODO: fix vibrancy + + // As per the Cocoa documentation, passing the high contrast names to + // appearanceNamed: will result in nil, so these can not be used. + NSAppearanceName name; + + if (newTheme->variation == GLFW_THEME_LIGHT) + { + if (newTheme->flags & GLFW_THEME_ATTRIBUTE_REDUCE_TRANSPARENCY) + name = NSAppearanceNameVibrantLight; + else + name = NSAppearanceNameAqua; + } + else + { + if (newTheme->flags & GLFW_THEME_ATTRIBUTE_REDUCE_TRANSPARENCY) + { + name = NSAppearanceNameVibrantDark; + } + else if (@available(macOS 10.14, *)) + { + name = NSAppearanceNameDarkAqua; + } + else + { + name = NSAppearanceNameAqua; + } + } + + NSAppearance* appearance = [NSAppearance appearanceNamed:name]; + [(NSWindow*)window->ns.object setAppearance:appearance]; + } + } + } +} + +void _glfwSetThemeCocoa(_GLFWwindow* window, const _GLFWtheme* theme) +{ + _GLFWtheme* currentTheme = &window->theme.internal; + _GLFWtheme newTheme; + + if (!theme) + _glfwInitDefaultTheme(&newTheme); + else + memcpy(&newTheme, theme, sizeof(_GLFWtheme)); + + replaceTheme(window, currentTheme, &newTheme); + + memcpy(currentTheme, &newTheme, sizeof(_GLFWtheme)); + + // Not available for setting in Cocoa. + currentTheme->flags &= ~(GLFW_THEME_ATTRIBUTE_HIGH_CONTRAST | + GLFW_THEME_ATTRIBUTE_REDUCE_TRANSPARENCY | + GLFW_THEME_ATTRIBUTE_REDUCE_MOTION| + GLFW_THEME_COLOR_MAIN); + + // TODO: NSColor controlAccentColor is not settable. Is there any reason in overriding a similar value? Does it apply to menu item highlights? If yes, then it must be overridden. +} + +_GLFWtheme* _glfwGetThemeCocoa(_GLFWwindow* window, int inlineDefaults) +{ + _GLFWtheme* theme = &window->theme.external; + memcpy(theme, &window->theme.internal, sizeof(_GLFWtheme)); + + // FIXME: fix not overriding specified properties. + if (!inlineDefaults) + return theme; + + theme->variation = GLFW_THEME_LIGHT; + theme->flags = 0; + + if (@available(macOS 10.09, *)) + { + NSAppearance* appearance = [(NSWindow*)window->ns.object appearance]; + + if (appearance == NULL) + appearance = [NSApp effectiveAppearance]; + + _glfwNSAppearanceToTheme(appearance, theme); + } + + _GLFWtheme systemTheme; + + _glfwGetSystemThemeCocoa(&systemTheme); + + if ((theme->flags & GLFW_THEME_COLOR_MAIN) == 0) + { + assert(systemTheme.flags & GLFW_THEME_COLOR_MAIN); + memcpy(&theme->color, &systemTheme.color, sizeof(float) * 4); + } + + theme->flags |= systemTheme.flags; + + return theme; +} + EGLenum _glfwGetEGLPlatformCocoa(EGLint** attribs) { if (_glfw.egl.ANGLE_platform_angle) diff --git a/src/init.c b/src/init.c index 06dbb3f2..4cd18afa 100644 --- a/src/init.c +++ b/src/init.c @@ -549,3 +549,25 @@ GLFWAPI GLFWerrorfun glfwSetErrorCallback(GLFWerrorfun cbfun) return cbfun; } +GLFWAPI const GLFWtheme* glfwGetSystemDefaultTheme() +{ + _GLFWtheme* theme; + _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + + theme = _glfw.platform.getSystemDefaultTheme(); + assert(theme->variation != GLFW_THEME_DEFAULT); + + return (const GLFWtheme*) theme; +} + +GLFWAPI GLFWthemefun glfwSetSystemThemeCallback(GLFWthemefun callback) +{ + + _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + + //return _glfw.platform.setSystemThemeCallback(callback + + _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + _GLFW_SWAP(GLFWthemefun, _glfw.callbacks.theme, callback); + return callback; +} diff --git a/src/input.c b/src/input.c index 8b7ef29c..3adda292 100644 --- a/src/input.c +++ b/src/input.c @@ -478,6 +478,13 @@ void _glfwInputJoystickHat(_GLFWjoystick* js, int hat, char value) js->hats[hat] = value; } +void _glfwInputSystemTheme(_GLFWtheme* theme) +{ + assert(theme != NULL); + + if (_glfw.callbacks.theme) + _glfw.callbacks.theme((GLFWtheme*) theme); +} ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// diff --git a/src/internal.h b/src/internal.h index fe0369aa..eaa71084 100644 --- a/src/internal.h +++ b/src/internal.h @@ -72,6 +72,7 @@ typedef struct _GLFWplatform _GLFWplatform; typedef struct _GLFWlibrary _GLFWlibrary; typedef struct _GLFWmonitor _GLFWmonitor; typedef struct _GLFWcursor _GLFWcursor; +typedef struct _GLFWtheme _GLFWtheme; typedef struct _GLFWmapelement _GLFWmapelement; typedef struct _GLFWmapping _GLFWmapping; typedef struct _GLFWjoystick _GLFWjoystick; @@ -518,6 +519,20 @@ struct _GLFWcontext GLFW_PLATFORM_CONTEXT_STATE }; +// Theme structure +// +struct _GLFWtheme +{ + // The light/dark variation. If set to DEFAULT, use the system theme variation. + int variation; + + int flags; + + // When applying a theme, ignore this color if the HAS_COLOR + // flag is unset, and use the system theme color instead. + float color[4]; +}; + // Window and context structure // struct _GLFWwindow @@ -573,6 +588,14 @@ struct _GLFWwindow GLFWcharmodsfun charmods; GLFWdropfun drop; } callbacks; + + struct { + // Clients can mutate this theme data at any time + _GLFWtheme external; + + // The window's user-specified theme settings. + _GLFWtheme internal; // TODO: init to defaults on window creation. TODO: set in glfwSetTheme + } theme; // This is defined in platform.h GLFW_PLATFORM_WINDOW_STATE @@ -673,10 +696,11 @@ struct _GLFWmutex struct _GLFWplatform { int platformID; - // init + // Init GLFWbool (*init)(void); void (*terminate)(void); - // input + _GLFWtheme* (*getSystemDefaultTheme)(void); + // Input void (*getCursorPos)(_GLFWwindow*,double*,double*); void (*setCursorPos)(_GLFWwindow*,double,double); void (*setCursorMode)(_GLFWwindow*,int); @@ -695,7 +719,7 @@ struct _GLFWplatform GLFWbool (*pollJoystick)(_GLFWjoystick*,int); const char* (*getMappingName)(void); void (*updateGamepadGUID)(char*); - // monitor + // Monitor void (*freeMonitor)(_GLFWmonitor*); void (*getMonitorPos)(_GLFWmonitor*,int*,int*); void (*getMonitorContentScale)(_GLFWmonitor*,float*,float*); @@ -704,7 +728,7 @@ struct _GLFWplatform void (*getVideoMode)(_GLFWmonitor*,GLFWvidmode*); GLFWbool (*getGammaRamp)(_GLFWmonitor*,GLFWgammaramp*); void (*setGammaRamp)(_GLFWmonitor*,const GLFWgammaramp*); - // window + // Window GLFWbool (*createWindow)(_GLFWwindow*,const _GLFWwndconfig*,const _GLFWctxconfig*,const _GLFWfbconfig*); void (*destroyWindow)(_GLFWwindow*); void (*setWindowTitle)(_GLFWwindow*,const char*); @@ -738,6 +762,9 @@ struct _GLFWplatform void (*setWindowFloating)(_GLFWwindow*,GLFWbool); void (*setWindowOpacity)(_GLFWwindow*,float); void (*setWindowMousePassthrough)(_GLFWwindow*,GLFWbool); + _GLFWtheme* (*getTheme)(_GLFWwindow*,int); + void (*setTheme)(_GLFWwindow*,const _GLFWtheme*); + // Events void (*pollEvents)(void); void (*waitEvents)(void); void (*waitEventsTimeout)(double); @@ -746,7 +773,7 @@ struct _GLFWplatform EGLenum (*getEGLPlatform)(EGLint**); EGLNativeDisplayType (*getEGLNativeDisplay)(void); EGLNativeWindowType (*getEGLNativeWindow)(_GLFWwindow*); - // vulkan + // Vulkan void (*getRequiredInstanceExtensions)(char**); GLFWbool (*getPhysicalDevicePresentationSupport)(VkInstance,VkPhysicalDevice,uint32_t); VkResult (*createWindowSurface)(VkInstance,_GLFWwindow*,const VkAllocationCallbacks*,VkSurfaceKHR*); @@ -866,7 +893,10 @@ struct _GLFWlibrary struct { GLFWmonitorfun monitor; GLFWjoystickfun joystick; + GLFWthemefun theme; } callbacks; + + _GLFWtheme theme; // These are defined in platform.h GLFW_PLATFORM_LIBRARY_WINDOW_STATE @@ -932,6 +962,8 @@ void _glfwInputJoystickAxis(_GLFWjoystick* js, int axis, float value); void _glfwInputJoystickButton(_GLFWjoystick* js, int button, char value); void _glfwInputJoystickHat(_GLFWjoystick* js, int hat, char value); +void _glfwInputSystemTheme(_GLFWtheme* theme); + void _glfwInputMonitor(_GLFWmonitor* monitor, int action, int placement); void _glfwInputMonitorWindow(_GLFWmonitor* monitor, _GLFWwindow* window); @@ -1010,3 +1042,4 @@ void* _glfw_calloc(size_t count, size_t size); void* _glfw_realloc(void* pointer, size_t size); void _glfw_free(void* pointer); +void _glfwInitDefaultTheme(_GLFWtheme* theme); diff --git a/src/null_init.c b/src/null_init.c index 7236c98c..f70067fd 100644 --- a/src/null_init.c +++ b/src/null_init.c @@ -42,8 +42,11 @@ GLFWbool _glfwConnectNull(int platformID, _GLFWplatform* platform) const _GLFWplatform null = { GLFW_PLATFORM_NULL, + // Init _glfwInitNull, _glfwTerminateNull, + _glfwGetSystemDefaultThemeNull, + // Input _glfwGetCursorPosNull, _glfwSetCursorPosNull, _glfwSetCursorModeNull, @@ -62,6 +65,7 @@ GLFWbool _glfwConnectNull(int platformID, _GLFWplatform* platform) _glfwPollJoystickNull, _glfwGetMappingNameNull, _glfwUpdateGamepadGUIDNull, + // Monitor _glfwFreeMonitorNull, _glfwGetMonitorPosNull, _glfwGetMonitorContentScaleNull, @@ -70,6 +74,7 @@ GLFWbool _glfwConnectNull(int platformID, _GLFWplatform* platform) _glfwGetVideoModeNull, _glfwGetGammaRampNull, _glfwSetGammaRampNull, + // Window _glfwCreateWindowNull, _glfwDestroyWindowNull, _glfwSetWindowTitleNull, @@ -103,13 +108,17 @@ GLFWbool _glfwConnectNull(int platformID, _GLFWplatform* platform) _glfwSetWindowFloatingNull, _glfwSetWindowOpacityNull, _glfwSetWindowMousePassthroughNull, + _glfwGetThemeNull, + _glfwSetThemeNull, _glfwPollEventsNull, _glfwWaitEventsNull, _glfwWaitEventsTimeoutNull, _glfwPostEmptyEventNull, + // EGL _glfwGetEGLPlatformNull, _glfwGetEGLNativeDisplayNull, _glfwGetEGLNativeWindowNull, + // Vulkan _glfwGetRequiredInstanceExtensionsNull, _glfwGetPhysicalDevicePresentationSupportNull, _glfwCreateWindowSurfaceNull, @@ -264,3 +273,12 @@ void _glfwTerminateNull(void) _glfwTerminateEGL(); } +_GLFWtheme* _glfwGetSystemDefaultThemeNull(void) +{ + _glfwInitDefaultTheme(&_glfw.theme); + + // DEFAULT is not a valid return value. + _glfw.theme.variation = GLFW_THEME_LIGHT; + + return &_glfw.theme; +} diff --git a/src/null_platform.h b/src/null_platform.h index fb9374b4..06982b85 100644 --- a/src/null_platform.h +++ b/src/null_platform.h @@ -270,3 +270,6 @@ VkResult _glfwCreateWindowSurfaceNull(VkInstance instance, _GLFWwindow* window, void _glfwPollMonitorsNull(void); +_GLFWtheme* _glfwGetSystemDefaultThemeNull(void); +void _glfwSetThemeNull(_GLFWwindow* window, const _GLFWtheme* theme); +_GLFWtheme* _glfwGetThemeNull(_GLFWwindow* window, int inlineDefaults); diff --git a/src/null_window.c b/src/null_window.c index e0bbb3b6..187a7126 100644 --- a/src/null_window.c +++ b/src/null_window.c @@ -30,6 +30,7 @@ #include "internal.h" #include +#include static void applySizeLimits(_GLFWwindow* window, int* width, int* height) { @@ -551,6 +552,18 @@ const char* _glfwGetClipboardStringNull(void) return _glfw.null.clipboardString; } +void _glfwSetThemeNull(_GLFWwindow* window, const _GLFWtheme* theme) +{ + memcpy(&window->theme.internal, theme, sizeof(_GLFWtheme)); +} + +_GLFWtheme* _glfwGetThemeNull(_GLFWwindow* window, int inlineDefaults) +{ + memcpy(&window->theme.external, &window->theme.internal, sizeof(_GLFWtheme)); + + return &window->theme.external; +} + EGLenum _glfwGetEGLPlatformNull(EGLint** attribs) { return 0; diff --git a/src/theme.c b/src/theme.c new file mode 100644 index 00000000..d790838d --- /dev/null +++ b/src/theme.c @@ -0,0 +1,145 @@ +//======================================================================== +// GLFW 3.4 - www.glfw.org +//------------------------------------------------------------------------ +// Copyright (c) 2023 Andreas O. Jansen +// +// 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. + +#include "internal.h" + +#include +#include +#include +#include +#include + + +static GLFWbool attributesAreValid(int attributes) +{ + return (attributes & ~(GLFW_THEME_ATTRIBUTE_HIGH_CONTRAST | + GLFW_THEME_ATTRIBUTE_REDUCE_TRANSPARENCY | + GLFW_THEME_ATTRIBUTE_REDUCE_MOTION | + GLFW_THEME_COLOR_MAIN)) == 0; +} + +void _glfwInitDefaultTheme(_GLFWtheme* theme) +{ + theme->variation = GLFW_THEME_DEFAULT; + theme->flags = 0; +} + + +GLFWAPI GLFWtheme* glfwCreateTheme(void) +{ + _GLFWtheme* theme = _glfw_calloc(1, sizeof(_GLFWtheme)); + _glfwInitDefaultTheme(theme); + + return (GLFWtheme*) theme; +} + +GLFWAPI void glfwDestroyTheme(GLFWtheme* theme) +{ + _glfw_free((_GLFWtheme*) theme); +} + +GLFWAPI void glfwCopyTheme(const GLFWtheme* source, GLFWtheme* target) +{ + memcpy(target, source, sizeof(_GLFWtheme)); +} + +GLFWAPI int glfwThemeEqual(const GLFWtheme* first, const GLFWtheme* second) +{ + _GLFWtheme* _first = (_GLFWtheme*) first; + _GLFWtheme* _second = (_GLFWtheme*) second; + + if (_first->variation != _second->variation) + return GLFW_FALSE; + + if (_first->flags != _second->flags) + return GLFW_FALSE; + + if (_first->flags & GLFW_THEME_COLOR_MAIN) + return memcmp(&_first->color, &_second->color, sizeof(float) * 4); + + return GLFW_TRUE; +} + +GLFWAPI int glfwThemeGetVariation(const GLFWtheme* theme) +{ + assert(theme != NULL); + return ((_GLFWtheme*) theme)->variation; +} + +GLFWAPI void glfwThemeSetVariation(GLFWtheme* theme, int value) +{ + assert(theme != NULL); + assert(value == GLFW_THEME_DARK || value == GLFW_THEME_DEFAULT || value == GLFW_THEME_LIGHT); + + ((_GLFWtheme*) theme)->variation = value; +} + +GLFWAPI int glfwThemeGetAttribute(const GLFWtheme* theme, int attribute) +{ + assert(theme != NULL); + assert(attributesAreValid(attribute)); + + return ((_GLFWtheme*) theme)->flags & attribute ? GLFW_TRUE : GLFW_FALSE; +} + +GLFWAPI void glfwThemeSetAttribute(GLFWtheme* theme, int attribute, int value) +{ + _GLFWtheme* _theme = (_GLFWtheme*) theme; + + assert(theme != NULL); + assert(value == GLFW_TRUE || value == GLFW_FALSE); + assert(attributesAreValid(attribute)); + + if (value == GLFW_TRUE) + _theme->flags |= attribute; + else + _theme->flags &= ~attribute; +} + +GLFWAPI void glfwThemeGetColor(const GLFWtheme* theme, int specifier, float* red, float* green, float* blue, float* alpha) +{ + const float* color = ((_GLFWtheme*) theme)->color; + + assert(theme != NULL); + assert(specifier == GLFW_THEME_COLOR_MAIN); + assert(red != NULL && green != NULL && blue != NULL && alpha != NULL); + + *red = color[0]; + *green = color[1]; + *blue = color[2]; + *alpha = color[3]; +} + +GLFWAPI void glfwThemeSetColor(GLFWtheme* theme, int specifier, float red, float green, float blue, float alpha) +{ + float* color = ((_GLFWtheme*) theme)->color; + + assert(theme != NULL); + assert(specifier == GLFW_THEME_COLOR_MAIN); + + color[0] = red; + color[1] = green; + color[2] = blue; + color[3] = alpha; +} diff --git a/src/win32_init.c b/src/win32_init.c index ef2615f1..2cab8c0d 100644 --- a/src/win32_init.c +++ b/src/win32_init.c @@ -151,6 +151,8 @@ static GLFWbool loadLibraries(void) _glfwPlatformGetModuleSymbol(_glfw.win32.dwmapi.instance, "DwmEnableBlurBehindWindow"); _glfw.win32.dwmapi.GetColorizationColor = (PFN_DwmGetColorizationColor) _glfwPlatformGetModuleSymbol(_glfw.win32.dwmapi.instance, "DwmGetColorizationColor"); + _glfw.win32.dwmapi.SetWindowAttribute = (PFN_DwmSetWindowAttribute) + _glfwPlatformGetModuleSymbol(_glfw.win32.dwmapi.instance, "DwmSetWindowAttribute"); } _glfw.win32.shcore.instance = _glfwPlatformLoadModule("shcore.dll"); @@ -169,6 +171,18 @@ static GLFWbool loadLibraries(void) _glfwPlatformGetModuleSymbol(_glfw.win32.ntdll.instance, "RtlVerifyVersionInfo"); } + _glfw.win32.uxtheme.instance = _glfwPlatformLoadModule("uxtheme.dll"); + if (_glfw.win32.uxtheme.instance) + { + _glfw.win32.uxtheme.ShouldAppsUseDarkMode = (ShouldAppsUseDarkModePtr)_glfwPlatformGetModuleSymbol(_glfw.win32.uxtheme.instance, MAKEINTRESOURCEA(132)); + _glfw.win32.uxtheme.GetImmersiveColorFromColorSetEx = (GetImmersiveColorFromColorSetExPtr)_glfwPlatformGetModuleSymbol(_glfw.win32.uxtheme.instance, MAKEINTRESOURCEA(95)); + _glfw.win32.uxtheme.GetImmersiveColorTypeFromName = (GetImmersiveColorTypeFromNamePtr)_glfwPlatformGetModuleSymbol(_glfw.win32.uxtheme.instance, MAKEINTRESOURCEA(96)); + _glfw.win32.uxtheme.GetImmersiveUserColorSetPreference = (GetImmersiveUserColorSetPreferencePtr)_glfwPlatformGetModuleSymbol(_glfw.win32.uxtheme.instance, MAKEINTRESOURCEA(98)); + + _glfw.win32.uxtheme.uxThemeAvailable = _glfw.win32.uxtheme.ShouldAppsUseDarkMode && _glfw.win32.uxtheme.GetImmersiveColorFromColorSetEx && _glfw.win32.uxtheme.GetImmersiveColorTypeFromName && _glfw.win32.uxtheme.GetImmersiveUserColorSetPreference; + _glfw.win32.uxtheme.darkTitleAvailable = _glfwIsWindows10BuildOrGreaterWin32(22000); + } + return GLFW_TRUE; } @@ -193,6 +207,9 @@ static void freeLibraries(void) if (_glfw.win32.ntdll.instance) _glfwPlatformFreeModule(_glfw.win32.ntdll.instance); + + if (_glfw.win32.uxtheme.instance) + _glfwPlatformFreeModule(_glfw.win32.uxtheme.instance); } // Create key code translation tables @@ -606,8 +623,11 @@ GLFWbool _glfwConnectWin32(int platformID, _GLFWplatform* platform) const _GLFWplatform win32 = { GLFW_PLATFORM_WIN32, + // Init _glfwInitWin32, _glfwTerminateWin32, + _glfwGetSystemDefaultThemeWin32, + // Input _glfwGetCursorPosWin32, _glfwSetCursorPosWin32, _glfwSetCursorModeWin32, @@ -626,6 +646,7 @@ GLFWbool _glfwConnectWin32(int platformID, _GLFWplatform* platform) _glfwPollJoystickWin32, _glfwGetMappingNameWin32, _glfwUpdateGamepadGUIDWin32, + // Monitor _glfwFreeMonitorWin32, _glfwGetMonitorPosWin32, _glfwGetMonitorContentScaleWin32, @@ -634,6 +655,7 @@ GLFWbool _glfwConnectWin32(int platformID, _GLFWplatform* platform) _glfwGetVideoModeWin32, _glfwGetGammaRampWin32, _glfwSetGammaRampWin32, + // Window _glfwCreateWindowWin32, _glfwDestroyWindowWin32, _glfwSetWindowTitleWin32, @@ -667,13 +689,18 @@ GLFWbool _glfwConnectWin32(int platformID, _GLFWplatform* platform) _glfwSetWindowFloatingWin32, _glfwSetWindowOpacityWin32, _glfwSetWindowMousePassthroughWin32, + _glfwGetThemeWin32, + _glfwSetThemeWin32, + // Events _glfwPollEventsWin32, _glfwWaitEventsWin32, _glfwWaitEventsTimeoutWin32, _glfwPostEmptyEventWin32, + // EGL _glfwGetEGLPlatformWin32, _glfwGetEGLNativeDisplayWin32, _glfwGetEGLNativeWindowWin32, + // Vulkan _glfwGetRequiredInstanceExtensionsWin32, _glfwGetPhysicalDevicePresentationSupportWin32, _glfwCreateWindowSurfaceWin32, @@ -727,5 +754,20 @@ void _glfwTerminateWin32(void) freeLibraries(); } -#endif // _GLFW_WIN32 +_GLFWtheme* _glfwGetSystemDefaultThemeWin32(void) +{ + _GLFWtheme* theme = &_glfw.theme; + theme->variation = GLFW_THEME_LIGHT; + theme->flags = 0; + + if (_glfw.win32.uxtheme.uxThemeAvailable && _glfw.win32.uxtheme.darkTitleAvailable) { + if (_glfw.win32.uxtheme.ShouldAppsUseDarkMode() & 0x1) { + theme->variation = GLFW_THEME_DARK; + } + } + + return theme; +} + +#endif // _GLFW_WIN32 diff --git a/src/win32_platform.h b/src/win32_platform.h index 82b34bb9..b5f048e2 100644 --- a/src/win32_platform.h +++ b/src/win32_platform.h @@ -299,12 +299,15 @@ typedef int (WINAPI * PFN_GetSystemMetricsForDpi)(int,UINT); // dwmapi.dll function pointer typedefs typedef HRESULT (WINAPI * PFN_DwmIsCompositionEnabled)(BOOL*); typedef HRESULT (WINAPI * PFN_DwmFlush)(VOID); -typedef HRESULT(WINAPI * PFN_DwmEnableBlurBehindWindow)(HWND,const DWM_BLURBEHIND*); +typedef HRESULT (WINAPI * PFN_DwmEnableBlurBehindWindow)(HWND,const DWM_BLURBEHIND*); typedef HRESULT (WINAPI * PFN_DwmGetColorizationColor)(DWORD*,BOOL*); +typedef HRESULT (WINAPI * PFN_DwmSetWindowAttribute)(HWND,DWORD,LPCVOID,DWORD); + #define DwmIsCompositionEnabled _glfw.win32.dwmapi.IsCompositionEnabled #define DwmFlush _glfw.win32.dwmapi.Flush #define DwmEnableBlurBehindWindow _glfw.win32.dwmapi.EnableBlurBehindWindow #define DwmGetColorizationColor _glfw.win32.dwmapi.GetColorizationColor +#define DwmSetWindowAttribute _glfw.win32.dwmapi.SetWindowAttribute // shcore.dll function pointer typedefs typedef HRESULT (WINAPI * PFN_SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS); @@ -366,6 +369,11 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR)( #define GLFW_WGL_CONTEXT_STATE _GLFWcontextWGL wgl; #define GLFW_WGL_LIBRARY_CONTEXT_STATE _GLFWlibraryWGL wgl; +typedef BOOL (WINAPI * ShouldAppsUseDarkModePtr)(); +typedef DWORD (WINAPI * GetImmersiveColorFromColorSetExPtr)(UINT,UINT,BOOL,UINT); +typedef int (WINAPI * GetImmersiveColorTypeFromNamePtr)(const WCHAR*); +typedef int (WINAPI * GetImmersiveUserColorSetPreferencePtr)(BOOL,BOOL); + // WGL-specific per-context data // @@ -487,6 +495,7 @@ typedef struct _GLFWlibraryWin32 PFN_DwmFlush Flush; PFN_DwmEnableBlurBehindWindow EnableBlurBehindWindow; PFN_DwmGetColorizationColor GetColorizationColor; + PFN_DwmSetWindowAttribute SetWindowAttribute; } dwmapi; struct { @@ -499,6 +508,16 @@ typedef struct _GLFWlibraryWin32 HINSTANCE instance; PFN_RtlVerifyVersionInfo RtlVerifyVersionInfo_; } ntdll; + + struct { + HINSTANCE instance; + GLFWbool uxThemeAvailable; + GLFWbool darkTitleAvailable; + ShouldAppsUseDarkModePtr ShouldAppsUseDarkMode; + GetImmersiveColorFromColorSetExPtr GetImmersiveColorFromColorSetEx; + GetImmersiveColorTypeFromNamePtr GetImmersiveColorTypeFromName; + GetImmersiveUserColorSetPreferencePtr GetImmersiveUserColorSetPreference; + } uxtheme; } _GLFWlibraryWin32; // Win32-specific per-monitor data @@ -622,3 +641,6 @@ GLFWbool _glfwCreateContextWGL(_GLFWwindow* window, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig); +_GLFWtheme* _glfwGetSystemDefaultThemeWin32(void); +void _glfwSetThemeWin32(_GLFWwindow* window, const _GLFWtheme* theme); +_GLFWtheme* _glfwGetThemeWin32(_GLFWwindow* window, int inlineDefaults); diff --git a/src/win32_window.c b/src/win32_window.c index a4a18171..90f2a6de 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -37,6 +37,47 @@ #include #include +// Ref: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +// Apply the system default theme +// +static void applySystemTheme(HWND handle) +{ + if (_glfw.win32.uxtheme.uxThemeAvailable && _glfw.win32.uxtheme.darkTitleAvailable) + { + GLFWbool value = _glfw.win32.uxtheme.ShouldAppsUseDarkMode() & 0x1; + DwmSetWindowAttribute(handle, + DWMWA_USE_IMMERSIVE_DARK_MODE, + &value, + sizeof(value)); + } +} + + +static int getAccentColor(float color[4]) +{ + if (!_glfw.win32.uxtheme.uxThemeAvailable) + return GLFW_FALSE; + + UINT dwImmersiveColorType = _glfw.win32.uxtheme.GetImmersiveColorTypeFromName(L"ImmersiveSystemAccent"); + UINT dwImmersiveColorSet = _glfw.win32.uxtheme.GetImmersiveUserColorSetPreference(FALSE, FALSE); + + UINT rgba = _glfw.win32.uxtheme.GetImmersiveColorFromColorSetEx(dwImmersiveColorSet, + dwImmersiveColorType, + FALSE, + 0); + + color[0] = (float) (0xFF & rgba); + color[1] = (float) ((0xFF00 & rgba) >> 8); + color[2] = (float) ((0xFF0000 & rgba) >> 16); + color[3] = (float) ((0xFF000000 & rgba) >> 24); + + return GLFW_TRUE; +} + // Returns the window style for the specified window // static DWORD getWindowStyle(const _GLFWwindow* window) @@ -1146,6 +1187,13 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l return 0; } + case WM_THEMECHANGED: + case WM_SETTINGCHANGE: { + if (window->theme.internal.variation == GLFW_THEME_DEFAULT) { + applySystemTheme(window->win32.handle); + } + } break; + case WM_GETDPISCALEDSIZE: { if (window->win32.scaleToMonitor) @@ -1436,6 +1484,9 @@ static int createNativeWindow(_GLFWwindow* window, _glfwGetWindowSizeWin32(window, &window->win32.width, &window->win32.height); + // Use the system default when creating a window + applySystemTheme(window->win32.handle); + return GLFW_TRUE; } @@ -2373,6 +2424,68 @@ const char* _glfwGetClipboardStringWin32(void) return _glfw.win32.clipboardString; } +void _glfwSetThemeWin32(_GLFWwindow* window, const _GLFWtheme* theme) +{ + _GLFWtheme* currentTheme = &window->theme.internal; + _GLFWtheme newTheme; + + if (!theme) + _glfwInitDefaultTheme(&newTheme); + else + memcpy(&newTheme, theme, sizeof(_GLFWtheme)); + + if (newTheme.variation == GLFW_THEME_DEFAULT) + { + applySystemTheme(window->win32.handle); + } + else + { + GLFWbool darkMode = newTheme.variation == GLFW_THEME_DARK; + + DwmSetWindowAttribute(window->win32.handle, + DWMWA_USE_IMMERSIVE_DARK_MODE, + &darkMode, + sizeof(darkMode)); + } + + // TODO: set accent color + + memcpy(currentTheme, &newTheme, sizeof(_GLFWtheme)); + + // Not available for setting in Win32. + currentTheme->flags &= ~(GLFW_THEME_ATTRIBUTE_HIGH_CONTRAST | + GLFW_THEME_ATTRIBUTE_REDUCE_TRANSPARENCY | + GLFW_THEME_ATTRIBUTE_REDUCE_MOTION| + GLFW_THEME_COLOR_MAIN); +} + +_GLFWtheme* _glfwGetThemeWin32(_GLFWwindow* window, int inlineDefaults) +{ + _GLFWtheme* theme = &window->theme.external; + memcpy(theme, &window->theme.internal, sizeof(_GLFWtheme)); + + if (!inlineDefaults) + return theme; + + if (theme->variation == GLFW_THEME_DEFAULT) + { + theme->variation = GLFW_THEME_LIGHT; + + if (_glfw.win32.uxtheme.uxThemeAvailable && _glfw.win32.uxtheme.darkTitleAvailable) + theme->variation = GLFW_THEME_DARK; + } + + if ((theme->flags & GLFW_THEME_COLOR_MAIN) == 0) + { + if (getAccentColor(theme->color)) + theme->flags |= GLFW_THEME_COLOR_MAIN; + else + memset(&theme->color, 0, sizeof(float) * 4); + } + + return theme; +} + EGLenum _glfwGetEGLPlatformWin32(EGLint** attribs) { if (_glfw.egl.ANGLE_platform_angle) @@ -2501,4 +2614,3 @@ GLFWAPI HWND glfwGetWin32Window(GLFWwindow* handle) } #endif // _GLFW_WIN32 - diff --git a/src/window.c b/src/window.c index 1c8519ff..174fc15f 100644 --- a/src/window.c +++ b/src/window.c @@ -244,6 +244,8 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height, window->maxheight = GLFW_DONT_CARE; window->numer = GLFW_DONT_CARE; window->denom = GLFW_DONT_CARE; + + _glfwInitDefaultTheme(&window->theme.internal); if (!_glfw.platform.createWindow(window, &wndconfig, &ctxconfig, &fbconfig)) { @@ -1153,3 +1155,21 @@ GLFWAPI void glfwPostEmptyEvent(void) _glfw.platform.postEmptyEvent(); } +GLFWAPI void glfwSetTheme(GLFWwindow* handle, const GLFWtheme* theme) +{ + + _GLFWwindow* window = (_GLFWwindow*) handle; + assert(window != NULL); + + _GLFW_REQUIRE_INIT(); + _glfw.platform.setTheme(window, (_GLFWtheme*) theme); +} + +GLFWAPI GLFWtheme* glfwGetTheme(GLFWwindow* handle, int inlineDefaults) +{ + _GLFWwindow* window = (_GLFWwindow*) handle; + assert(window != NULL); + + _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + return (GLFWtheme*) _glfw.platform.getTheme(window, inlineDefaults); +} diff --git a/src/wl_init.c b/src/wl_init.c index 0ec65900..3d352852 100644 --- a/src/wl_init.c +++ b/src/wl_init.c @@ -408,8 +408,11 @@ GLFWbool _glfwConnectWayland(int platformID, _GLFWplatform* platform) const _GLFWplatform wayland = { GLFW_PLATFORM_WAYLAND, + // Init _glfwInitWayland, _glfwTerminateWayland, + _glfwGetSystemDefaultThemeWayland, + // Input _glfwGetCursorPosWayland, _glfwSetCursorPosWayland, _glfwSetCursorModeWayland, @@ -436,6 +439,7 @@ GLFWbool _glfwConnectWayland(int platformID, _GLFWplatform* platform) _glfwGetMappingNameNull, _glfwUpdateGamepadGUIDNull, #endif + // Monitor _glfwFreeMonitorWayland, _glfwGetMonitorPosWayland, _glfwGetMonitorContentScaleWayland, @@ -444,6 +448,7 @@ GLFWbool _glfwConnectWayland(int platformID, _GLFWplatform* platform) _glfwGetVideoModeWayland, _glfwGetGammaRampWayland, _glfwSetGammaRampWayland, + // Window _glfwCreateWindowWayland, _glfwDestroyWindowWayland, _glfwSetWindowTitleWayland, @@ -477,13 +482,18 @@ GLFWbool _glfwConnectWayland(int platformID, _GLFWplatform* platform) _glfwSetWindowFloatingWayland, _glfwSetWindowOpacityWayland, _glfwSetWindowMousePassthroughWayland, + _glfwGetThemeWayland, + _glfwSetThemeWayland, + // Events _glfwPollEventsWayland, _glfwWaitEventsWayland, _glfwWaitEventsTimeoutWayland, _glfwPostEmptyEventWayland, + // EGL _glfwGetEGLPlatformWayland, _glfwGetEGLNativeDisplayWayland, _glfwGetEGLNativeWindowWayland, + // Vulkan _glfwGetRequiredInstanceExtensionsWayland, _glfwGetPhysicalDevicePresentationSupportWayland, _glfwCreateWindowSurfaceWayland, @@ -944,5 +954,11 @@ void _glfwTerminateWayland(void) _glfw_free(_glfw.wl.clipboardString); } +_GLFWtheme* _glfwGetSystemDefaultThemeWayland(void) +{ + _glfwInputError(GLFW_FEATURE_UNIMPLEMENTED, NULL); + return NULL; // TODO: implement +} + #endif // _GLFW_WAYLAND diff --git a/src/wl_platform.h b/src/wl_platform.h index d00e28fe..be9edc42 100644 --- a/src/wl_platform.h +++ b/src/wl_platform.h @@ -686,3 +686,6 @@ void _glfwUpdateContentScaleWayland(_GLFWwindow* window); void _glfwAddSeatListenerWayland(struct wl_seat* seat); void _glfwAddDataDeviceListenerWayland(struct wl_data_device* device); +_GLFWtheme* _glfwGetSystemDefaultThemeWayland(void); +void _glfwSetThemeWayland(_GLFWwindow* window, const _GLFWtheme* theme); +_GLFWtheme* _glfwGetThemeWayland(_GLFWwindow* window, int inlineDefaults); diff --git a/src/wl_window.c b/src/wl_window.c index 7c509896..3310253d 100644 --- a/src/wl_window.c +++ b/src/wl_window.c @@ -3063,6 +3063,18 @@ const char* _glfwGetClipboardStringWayland(void) return _glfw.wl.clipboardString; } +void _glfwSetThemeWayland(_GLFWwindow* window, const _GLFWtheme* theme) +{ + _glfwInputError(GLFW_FEATURE_UNIMPLEMENTED, NULL); + // TODO: implement +} + +_GLFWtheme* _glfwGetThemeWayland(_GLFWwindow* window, int inlineDefaults) +{ + _glfwInputError(GLFW_FEATURE_UNIMPLEMENTED, NULL); + return NULL; // TODO: implement +} + EGLenum _glfwGetEGLPlatformWayland(EGLint** attribs) { if (_glfw.egl.EXT_platform_base && _glfw.egl.EXT_platform_wayland) diff --git a/src/x11_init.c b/src/x11_init.c index a0100f2f..582624b9 100644 --- a/src/x11_init.c +++ b/src/x11_init.c @@ -1169,8 +1169,11 @@ GLFWbool _glfwConnectX11(int platformID, _GLFWplatform* platform) const _GLFWplatform x11 = { GLFW_PLATFORM_X11, + // Init _glfwInitX11, _glfwTerminateX11, + _glfwGetSystemDefaultThemeX11, + // Input _glfwGetCursorPosX11, _glfwSetCursorPosX11, _glfwSetCursorModeX11, @@ -1197,6 +1200,7 @@ GLFWbool _glfwConnectX11(int platformID, _GLFWplatform* platform) _glfwGetMappingNameNull, _glfwUpdateGamepadGUIDNull, #endif + // Monitor _glfwFreeMonitorX11, _glfwGetMonitorPosX11, _glfwGetMonitorContentScaleX11, @@ -1205,6 +1209,7 @@ GLFWbool _glfwConnectX11(int platformID, _GLFWplatform* platform) _glfwGetVideoModeX11, _glfwGetGammaRampX11, _glfwSetGammaRampX11, + // Window _glfwCreateWindowX11, _glfwDestroyWindowX11, _glfwSetWindowTitleX11, @@ -1238,13 +1243,18 @@ GLFWbool _glfwConnectX11(int platformID, _GLFWplatform* platform) _glfwSetWindowFloatingX11, _glfwSetWindowOpacityX11, _glfwSetWindowMousePassthroughX11, + _glfwGetThemeX11, + _glfwSetThemeX11, + // Events _glfwPollEventsX11, _glfwWaitEventsX11, _glfwWaitEventsTimeoutX11, _glfwPostEmptyEventX11, + // EGL _glfwGetEGLPlatformX11, _glfwGetEGLNativeDisplayX11, _glfwGetEGLNativeWindowX11, + // Vulkan _glfwGetRequiredInstanceExtensionsX11, _glfwGetPhysicalDevicePresentationSupportX11, _glfwCreateWindowSurfaceX11, @@ -1654,5 +1664,11 @@ void _glfwTerminateX11(void) } } +_GLFWtheme* _glfwGetSystemDefaultThemeX11(void) +{ + _glfwInputError(GLFW_FEATURE_UNIMPLEMENTED, NULL); + return NULL; // TODO: implement +} + #endif // _GLFW_X11 diff --git a/src/x11_platform.h b/src/x11_platform.h index cdea3957..2a5aec12 100644 --- a/src/x11_platform.h +++ b/src/x11_platform.h @@ -1002,3 +1002,6 @@ GLFWbool _glfwChooseVisualGLX(const _GLFWwndconfig* wndconfig, const _GLFWfbconfig* fbconfig, Visual** visual, int* depth); +_GLFWtheme* _glfwGetSystemDefaultThemeX11(void); +void _glfwSetThemeX11(_GLFWwindow* window, const _GLFWtheme* theme); +_GLFWtheme* _glfwGetThemeX11(_GLFWwindow* window, int inlineDefaults); diff --git a/src/x11_window.c b/src/x11_window.c index 7da9b965..28320dea 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -3082,6 +3082,18 @@ const char* _glfwGetClipboardStringX11(void) return getSelectionString(_glfw.x11.CLIPBOARD); } +void _glfwSetThemeX11(_GLFWwindow* window, const _GLFWtheme* theme) +{ + _glfwInputError(GLFW_FEATURE_UNIMPLEMENTED, NULL); + // TODO: implement +} + +_GLFWtheme* _glfwGetThemeX11(_GLFWwindow* window, int inlineDefaults) +{ + _glfwInputError(GLFW_FEATURE_UNIMPLEMENTED, NULL); + return NULL; // TODO: implement +} + EGLenum _glfwGetEGLPlatformX11(EGLint** attribs) { if (_glfw.egl.ANGLE_platform_angle) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f81cfeb9..3125c1b5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -40,6 +40,7 @@ add_executable(timeout WIN32 MACOSX_BUNDLE timeout.c ${GLAD_GL}) add_executable(title WIN32 MACOSX_BUNDLE title.c ${GLAD_GL}) add_executable(triangle-vulkan WIN32 triangle-vulkan.c ${GLAD_VULKAN}) add_executable(window WIN32 MACOSX_BUNDLE window.c ${GLAD_GL}) +add_executable(theming WIN32 MACOSX_BUNDLE theming.c ${GLAD_GL}) target_link_libraries(empty Threads::Threads) target_link_libraries(threads Threads::Threads) @@ -49,7 +50,7 @@ if (RT_LIBRARY) endif() set(GUI_ONLY_BINARIES empty gamma icon inputlag joysticks tearing threads - timeout title triangle-vulkan window) + timeout title triangle-vulkan window theming) set(CONSOLE_BINARIES allocator clipboard events msaa glfwinfo iconify monitors reopen cursor) @@ -77,6 +78,7 @@ if (APPLE) set_target_properties(timeout PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Timeout") set_target_properties(title PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Title") set_target_properties(window PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Window") + set_target_properties(theming PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Theming") set_target_properties(${GUI_ONLY_BINARIES} PROPERTIES MACOSX_BUNDLE_SHORT_VERSION_STRING ${GLFW_VERSION} diff --git a/tests/theming.c b/tests/theming.c new file mode 100644 index 00000000..2e588761 --- /dev/null +++ b/tests/theming.c @@ -0,0 +1,245 @@ +//======================================================================== +// Theming test program +// Copyright (c) Camilla Löwy +// +// 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 theming features. +// +//======================================================================== + +#define GLAD_GL_IMPLEMENTATION +#include +#define GLFW_INCLUDE_NONE +#include + +#include +#include +#include + +const float theme_colors[6][4] = +{ + { 0, 0, 0, 1.0 }, // black + { 1.0, 0, 0, 1.0 }, // red + { 0, 1.0, 0, 1.0 }, // green + { 0, 0, 1.0, 1.0 }, // blue + { 1.0, 1.0, 1.0, 1.0 }, // white + { 0.5, 0.5, 0.5, 1.0 } // gray (no theme color) +}; + +static int cur_theme_color = 0; + +static GLFWtheme* theme; + +static void print_theme(const GLFWtheme* theme, const char* title) +{ + int n = 0; + int variation = glfwThemeGetVariation(theme); + + printf("%s: {\n", title); + printf(" Variation: %s\n", variation == GLFW_THEME_DEFAULT ? "default" : variation == GLFW_THEME_LIGHT ? "light" : "dark"); + printf(" Attributes: ["); + + if (glfwThemeGetAttribute(theme, GLFW_THEME_COLOR_MAIN)) + printf(n++ > 0 ? ", %s" : "%s", "COLOR_MAIN"); + + if (glfwThemeGetAttribute(theme, GLFW_THEME_ATTRIBUTE_HIGH_CONTRAST)) + printf(n++ > 0 ? ", %s" : "%s", "HIGH_CONTRAST"); + + if (glfwThemeGetAttribute(theme, GLFW_THEME_ATTRIBUTE_REDUCE_TRANSPARENCY)) + printf(n++ > 0 ? ", %s" : "%s", "REDUCE_TRANSPARENCY"); + + if (glfwThemeGetAttribute(theme, GLFW_THEME_ATTRIBUTE_REDUCE_MOTION)) + printf(n++ > 0 ? ", %s" : "%s", "REDUCE_MOTION"); + + printf("]\n"); + + if (glfwThemeGetAttribute(theme, GLFW_THEME_COLOR_MAIN)) + { + float r, g, b, a; + glfwThemeGetColor(theme, GLFW_THEME_COLOR_MAIN, &r, &g, &b, &a); + printf(" Main color: [%f, %f, %f, %f]\n", r, g, b, a); + } + printf("}\n"); +} + +static void set_theme(GLFWwindow* window, int theme_color) +{ + glfwThemeSetColor( + theme, + GLFW_THEME_COLOR_MAIN, + theme_colors[theme_color][0], + theme_colors[theme_color][1], + theme_colors[theme_color][2], + theme_colors[theme_color][3] + ); + + if (theme_color == 5) + glfwThemeSetAttribute(theme, GLFW_THEME_COLOR_MAIN, GLFW_FALSE); + else + glfwThemeSetAttribute(theme, GLFW_THEME_COLOR_MAIN, GLFW_TRUE); + + const char* title; + + switch (glfwThemeGetVariation(theme)) { + case GLFW_THEME_DEFAULT: + title = "Default theme"; + break; + case GLFW_THEME_LIGHT: + title = "Light theme"; + break; + case GLFW_THEME_DARK: + title = "Dark theme"; + break; + } + + glfwSetWindowTitle(window, title); + glfwSetTheme(window, theme); +} + +static void flip_attribute(int attribute) +{ + glfwThemeSetAttribute(theme, attribute, !glfwThemeGetAttribute(theme, attribute)); +} + +static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) +{ + if (action != GLFW_PRESS) + return; + + switch (key) + { + // No theme specified + case GLFW_KEY_0: + glfwSetWindowTitle(window, "Default theme (NULL)"); + glfwSetTheme(window, NULL); + break; + // Theme with default variation + case GLFW_KEY_1: + glfwThemeSetVariation(theme, GLFW_THEME_DEFAULT); + set_theme(window, cur_theme_color); + break; + // Theme with light variation + case GLFW_KEY_2: + glfwThemeSetVariation(theme, GLFW_THEME_LIGHT); + set_theme(window, cur_theme_color); + break; + // Theme with dark variation + case GLFW_KEY_3: + glfwThemeSetVariation(theme, GLFW_THEME_DARK); + set_theme(window, cur_theme_color); + break; + // Traverse colors + case GLFW_KEY_SPACE: + cur_theme_color = (cur_theme_color + 1) % 6; + set_theme(window, cur_theme_color); + break; + // Inspect + case GLFW_KEY_I: + print_theme(theme, "Constructed theme"); + break; + // Print + case GLFW_KEY_P: + print_theme(glfwGetTheme(window, GLFW_FALSE), "Specified window theme"); + print_theme(glfwGetTheme(window, GLFW_TRUE), "Active window theme"); + printf("\n"); + break; + // High contrast + case GLFW_KEY_H: + flip_attribute(GLFW_THEME_ATTRIBUTE_HIGH_CONTRAST); + set_theme(window, cur_theme_color); + break; + // Transparency + case GLFW_KEY_T: + flip_attribute(GLFW_THEME_ATTRIBUTE_REDUCE_TRANSPARENCY); + set_theme(window, cur_theme_color); + break; + // Motion + case GLFW_KEY_M: + flip_attribute(GLFW_THEME_ATTRIBUTE_REDUCE_MOTION); + set_theme(window, cur_theme_color); + break; + // Exit + case GLFW_KEY_ESCAPE: + glfwSetWindowShouldClose(window, GLFW_TRUE); + break; + } +} + +static void theme_callback(GLFWtheme* theme) +{ + print_theme(theme, "System theme changed to"); +} + +int main(int argc, char** argv) +{ + GLFWwindow* window; + + if (!glfwInit()) + { + fprintf(stderr, "Failed to initialize GLFW\n"); + exit(EXIT_FAILURE); + } + + print_theme(glfwGetSystemDefaultTheme(), "System theme"); + + window = glfwCreateWindow(200, 200, "Window Icon", NULL, NULL); + if (!window) + { + glfwTerminate(); + + fprintf(stderr, "Failed to open GLFW window\n"); + exit(EXIT_FAILURE); + } + + print_theme(glfwGetTheme(window, GLFW_FALSE), "Initial specified window theme"); + print_theme(glfwGetTheme(window, GLFW_TRUE), "Initial active window theme"); + printf("\n"); + + glfwMakeContextCurrent(window); + gladLoadGL(glfwGetProcAddress); + + glfwSetKeyCallback(window, key_callback); + + theme = glfwCreateTheme(); + set_theme(window, cur_theme_color); + + glfwSetSystemThemeCallback(theme_callback); + + while (!glfwWindowShouldClose(window)) + { + glClearColor( + theme_colors[cur_theme_color][0], + theme_colors[cur_theme_color][1], + theme_colors[cur_theme_color][2], + theme_colors[cur_theme_color][3] + ); + glClear(GL_COLOR_BUFFER_BIT); + glfwSwapBuffers(window); + glfwWaitEvents(); + } + + glfwDestroyTheme(theme); + glfwDestroyWindow(window); + glfwTerminate(); + exit(EXIT_SUCCESS); +}