From 11e47f08b14436c9759dcfb87af213c0696667db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Camilla=20L=C3=B6wy?= Date: Mon, 25 Sep 2017 23:14:45 +0200 Subject: [PATCH] Add glfwGetWindowOpacity and glfwSetWindowOpacity This adds support for setting the opacity of the whole window, including any decorations. Fixes #1089. --- README.md | 6 ++- docs/news.dox | 11 +++-- docs/window.dox | 33 ++++++++++++++ include/GLFW/glfw3.h | 56 +++++++++++++++++++++++ src/cocoa_window.m | 10 ++++ src/internal.h | 2 + src/mir_window.c | 9 ++++ src/null_window.c | 9 ++++ src/win32_window.c | 33 ++++++++++++++ src/window.c | 28 ++++++++++++ src/wl_window.c | 9 ++++ src/x11_init.c | 9 ++++ src/x11_platform.h | 2 + src/x11_window.c | 37 +++++++++++++-- tests/CMakeLists.txt | 6 ++- tests/opacity.c | 106 +++++++++++++++++++++++++++++++++++++++++++ 16 files changed, 354 insertions(+), 12 deletions(-) create mode 100644 tests/opacity.c diff --git a/README.md b/README.md index 56a42b11..631b2042 100644 --- a/README.md +++ b/README.md @@ -152,8 +152,10 @@ information on what to include when reporting a bug. functions for accessing X11 primary selection (#894,#1056) - Added headless [OSMesa](http://mesa3d.org/osmesa.html) backend (#850) - Added definition of `GLAPIENTRY` to public header -- Added `GLFW_TRANSPARENT_FRAMEBUFFER` window hint for enabling window - framebuffer transparency (#197,#663,#715,#723,#1078) +- Added `GLFW_TRANSPARENT_FRAMEBUFFER` window hint and attribute for controlling + per-pixel framebuffer transparency (#197,#663,#715,#723,#1078) +- Added `glfwGetWindowOpacity` and `glfwSetWindowOpacity` for controlling whole + window transparency (#1089) - Added `GLFW_CENTER_CURSOR` window hint for controlling cursor centering (#749,#842) - Added `GLFW_JOYSTICK_HAT_BUTTONS` init hint (#889) diff --git a/docs/news.dox b/docs/news.dox index f570ec3d..adbb7724 100644 --- a/docs/news.dox +++ b/docs/news.dox @@ -94,12 +94,17 @@ be disabled with the @ref GLFW_JOYSTICK_HAT_BUTTONS init hint. @see @ref joystick_hat -@subsection news_33_transparent Support for transparent window framebuffer +@subsection news_33_transparent Support for transparent windows and framebuffers GLFW now supports the creation of windows with transparent framebuffers on systems with desktop compositing enabled with the @ref -GLFW_TRANSPARENT_FRAMEBUFFER window hint and attribute. Any window decorations -will still be opaque. +GLFW_TRANSPARENT_FRAMEBUFFER window hint and attribute. This hint must be set +before window creation and leaves any window decorations opaque. + +GLFW now also supports whole window transparency with @ref glfwGetWindowOpacity +and @ref glfwSetWindowOpacity. This value controls the opacity of the whole +window including decorations and unlike framebuffer transparency can be changed +at any time after window creation. @subsection news_33_centercursor Cursor centering window hint diff --git a/docs/window.dox b/docs/window.dox index 0e37e5e5..2c9cec91 100644 --- a/docs/window.dox +++ b/docs/window.dox @@ -1091,6 +1091,14 @@ the window or framebuffer is resized. @subsection window_transparency Window transparency +GLFW supports two kinds of transparency for windows; framebuffer transparency +and whole window transparency. A single window may not use both methods. The +results of doing this are undefined. + +Both methods require the platform to support it and not every version of every +platform GLFW supports does this, so there are mechanisms to check whether the +window really is transparent. + Window framebuffers can be made transparent on a per-pixel per-frame basis with the [GLFW_TRANSPARENT_FRAMEBUFFER](@ref GLFW_TRANSPARENT_FRAMEBUFFER_hint) window hint. @@ -1115,6 +1123,31 @@ if (glfwGetWindowAttrib(window, GLFW_TRANSPARENT_FRAMEBUFFER)) } @endcode +GLFW comes with an example that enabled framebuffer transparency called `gears`. + +The opacity of the whole window, including any decorations, can be set with @ref +glfwSetWindowOpacity. + +@code +glfwSetWindowOpacity(window, 0.5f); +@endcode + +The opacity (or alpha) value is a positive finite number between zero and one, +where 0 (zero) is fully transparent and 1 (one) is fully opaque. The initial +opacity value for newly created windows is 1. + +The current opacity of a window can be queried with @ref glfwGetWindowOpacity. + +@code +float opacity = glfwGetWindowOpacity(window); +@endcode + +If the system does not support whole window transparency, this function always +returns one. + +GLFW comes with a test program that lets you control whole window transparency +at run-time called `opacity`. + @subsection window_attribs Window attributes diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index a1e7df4b..9dc34779 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -2837,6 +2837,62 @@ GLFWAPI void glfwGetWindowFrameSize(GLFWwindow* window, int* left, int* top, int */ GLFWAPI void glfwGetWindowContentScale(GLFWwindow* window, float* xscale, float* yscale); +/*! @brief Returns the opacity of the whole window. + * + * This function returns the opacity of the window, including any decorations. + * + * The opacity (or alpha) value is a positive finite number between zero and + * one, where zero is fully transparent and one is fully opaque. If the system + * does not support whole window transparency, this function always returns one. + * + * The initial opacity value for newly created windows is one. + * + * @param[in] window The window to query. + * @return The opacity value of the specified window. + * + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref + * GLFW_PLATFORM_ERROR. + * + * @thread_safety This function must only be called from the main thread. + * + * @sa @ref window_transparency + * @sa @ref glfwSetWindowOpacity + * + * @since Added in version 3.3. + * + * @ingroup window + */ +GLFWAPI float glfwGetWindowOpacity(GLFWwindow* window); + +/*! @brief Sets the opacity of the whole window. + * + * This function sets the opacity of the window, including any decorations. + * + * The opacity (or alpha) value is a positive finite number between zero and + * one, where zero is fully transparent and one is fully opaque. + * + * The initial opacity value for newly created windows is one. + * + * A window created with framebuffer transparency may not use whole window + * transparency. The results of doing this are undefined. + * + * @param[in] window The window to set the opacity for. + * @param[in] opacity The desired opacity of the specified window. + * + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref + * GLFW_PLATFORM_ERROR. + * + * @thread_safety This function must only be called from the main thread. + * + * @sa @ref window_transparency + * @sa @ref glfwGetWindowOpacity + * + * @since Added in version 3.3. + * + * @ingroup window + */ +GLFWAPI void glfwSetWindowOpacity(GLFWwindow* window, float opacity); + /*! @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 5f2686b2..c952ddc6 100644 --- a/src/cocoa_window.m +++ b/src/cocoa_window.m @@ -1493,6 +1493,16 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) [window->ns.object setLevel:NSNormalWindowLevel]; } +float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) +{ + return (float) [window->ns.object alphaValue]; +} + +void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) +{ + [window->ns.object setAlphaValue:opacity]; +} + void _glfwPlatformPollEvents(void) { for (;;) diff --git a/src/internal.h b/src/internal.h index 4225e3fd..0a2cbc4d 100644 --- a/src/internal.h +++ b/src/internal.h @@ -687,9 +687,11 @@ int _glfwPlatformWindowIconified(_GLFWwindow* window); int _glfwPlatformWindowVisible(_GLFWwindow* window); int _glfwPlatformWindowMaximized(_GLFWwindow* window); int _glfwPlatformFramebufferTransparent(_GLFWwindow* window); +float _glfwPlatformGetWindowOpacity(_GLFWwindow* window); void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled); void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, GLFWbool enabled); void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled); +void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity); void _glfwPlatformPollEvents(void); void _glfwPlatformWaitEvents(void); diff --git a/src/mir_window.c b/src/mir_window.c index fa28d0cf..fd8f2914 100644 --- a/src/mir_window.c +++ b/src/mir_window.c @@ -648,6 +648,15 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) "Mir: Unsupported function %s", __PRETTY_FUNCTION__); } +float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) +{ + return 1.f; +} + +void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) +{ +} + void _glfwPlatformPollEvents(void) { EventNode* node = NULL; diff --git a/src/null_window.c b/src/null_window.c index 11ccb4f4..6eb4ac6e 100644 --- a/src/null_window.c +++ b/src/null_window.c @@ -182,6 +182,15 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) { } +float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) +{ + return 1.f; +} + +void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) +{ +} + void _glfwPlatformShowWindow(_GLFWwindow* window) { } diff --git a/src/win32_window.c b/src/win32_window.c index 564a99ea..78084b45 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -1591,6 +1591,39 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); } +float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) +{ + BYTE alpha; + DWORD flags; + + if ((GetWindowLongW(window->win32.handle, GWL_EXSTYLE) & WS_EX_LAYERED) && + GetLayeredWindowAttributes(window->win32.handle, NULL, &alpha, &flags)) + { + if (flags & LWA_ALPHA) + return alpha / 255.f; + } + + return 1.f; +} + +void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) +{ + if (opacity < 1.f) + { + const BYTE alpha = (BYTE) (255 * opacity); + DWORD style = GetWindowLongW(window->win32.handle, GWL_EXSTYLE); + style |= WS_EX_LAYERED; + SetWindowLongW(window->win32.handle, GWL_EXSTYLE, style); + SetLayeredWindowAttributes(window->win32.handle, 0, alpha, LWA_ALPHA); + } + else + { + DWORD style = GetWindowLongW(window->win32.handle, GWL_EXSTYLE); + style &= ~WS_EX_LAYERED; + SetWindowLongW(window->win32.handle, GWL_EXSTYLE, style); + } +} + void _glfwPlatformPollEvents(void) { MSG msg; diff --git a/src/window.c b/src/window.c index 8b927d98..f4468e16 100644 --- a/src/window.c +++ b/src/window.c @@ -648,6 +648,34 @@ GLFWAPI void glfwGetWindowContentScale(GLFWwindow* handle, _glfwPlatformGetWindowContentScale(window, xscale, yscale); } +GLFWAPI float glfwGetWindowOpacity(GLFWwindow* handle) +{ + _GLFWwindow* window = (_GLFWwindow*) handle; + assert(window != NULL); + + _GLFW_REQUIRE_INIT_OR_RETURN(1.f); + return _glfwPlatformGetWindowOpacity(window); +} + +GLFWAPI void glfwSetWindowOpacity(GLFWwindow* handle, float opacity) +{ + _GLFWwindow* window = (_GLFWwindow*) handle; + assert(window != NULL); + assert(opacity == opacity); + assert(opacity >= 0.f); + assert(opacity <= 1.f); + + _GLFW_REQUIRE_INIT(); + + if (opacity != opacity || opacity < 0.f || opacity > 1.f) + { + _glfwInputError(GLFW_INVALID_VALUE, "Invalid window opacity %f", opacity); + return; + } + + _glfwPlatformSetWindowOpacity(window, opacity); +} + GLFWAPI void glfwIconifyWindow(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; diff --git a/src/wl_window.c b/src/wl_window.c index 264cf379..b2ae7aae 100644 --- a/src/wl_window.c +++ b/src/wl_window.c @@ -692,6 +692,15 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) "Wayland: Window attribute setting not implemented yet"); } +float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) +{ + return 1.f; +} + +void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) +{ +} + void _glfwPlatformPollEvents(void) { handleEvents(0); diff --git a/src/x11_init.c b/src/x11_init.c index b603a15f..4c863c38 100644 --- a/src/x11_init.c +++ b/src/x11_init.c @@ -737,9 +737,18 @@ static GLFWbool initExtensions(void) XInternAtom(_glfw.x11.display, "_NET_WM_ICON_NAME", False); _glfw.x11.NET_WM_BYPASS_COMPOSITOR = XInternAtom(_glfw.x11.display, "_NET_WM_BYPASS_COMPOSITOR", False); + _glfw.x11.NET_WM_WINDOW_OPACITY = + XInternAtom(_glfw.x11.display, "_NET_WM_WINDOW_OPACITY", False); _glfw.x11.MOTIF_WM_HINTS = XInternAtom(_glfw.x11.display, "_MOTIF_WM_HINTS", False); + // The compositing manager selection name contains the screen number + { + char name[32]; + snprintf(name, sizeof(name), "_NET_WM_CM_S%u", _glfw.x11.screen); + _glfw.x11.NET_WM_CM_Sx = XInternAtom(_glfw.x11.display, name, False); + } + return GLFW_TRUE; } diff --git a/src/x11_platform.h b/src/x11_platform.h index 7eba441d..c5a11cf2 100644 --- a/src/x11_platform.h +++ b/src/x11_platform.h @@ -257,6 +257,8 @@ typedef struct _GLFWlibraryX11 Atom NET_WM_STATE_DEMANDS_ATTENTION; Atom NET_WM_BYPASS_COMPOSITOR; Atom NET_WM_FULLSCREEN_MONITORS; + Atom NET_WM_WINDOW_OPACITY; + Atom NET_WM_CM_Sx; Atom NET_ACTIVE_WINDOW; Atom NET_FRAME_EXTENTS; Atom NET_REQUEST_FRAME_EXTENTS; diff --git a/src/x11_window.c b/src/x11_window.c index d9d34b01..32fff63b 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -2449,11 +2449,7 @@ int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) if (!window->x11.transparent) return GLFW_FALSE; - // Check whether a compositing manager is running - char name[32]; - snprintf(name, sizeof(name), "_NET_WM_CM_S%u", _glfw.x11.screen); - const Atom selection = XInternAtom(_glfw.x11.display, name, False); - return XGetSelectionOwner(_glfw.x11.display, selection) != None; + return XGetSelectionOwner(_glfw.x11.display, _glfw.x11.NET_WM_CM_Sx) != None; } void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled) @@ -2559,6 +2555,37 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) XFlush(_glfw.x11.display); } +float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) +{ + float opacity = 1.f; + + if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.NET_WM_CM_Sx)) + { + CARD32* value = NULL; + + if (_glfwGetWindowPropertyX11(window->x11.handle, + _glfw.x11.NET_WM_WINDOW_OPACITY, + XA_CARDINAL, + (unsigned char**) &value)) + { + opacity = (float) (*value / (double) 0xffffffffu); + } + + if (value) + XFree(value); + } + + return opacity; +} + +void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) +{ + const CARD32 value = (CARD32) (0xffffffffu * (double) opacity); + XChangeProperty(_glfw.x11.display, window->x11.handle, + _glfw.x11.NET_WM_WINDOW_OPACITY, XA_CARDINAL, 32, + PropModeReplace, (unsigned char*) &value, 1); +} + void _glfwPlatformPollEvents(void) { _GLFWwindow* window; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 278127b6..52b08389 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -32,6 +32,7 @@ add_executable(gamma WIN32 MACOSX_BUNDLE gamma.c ${GLAD}) add_executable(icon WIN32 MACOSX_BUNDLE icon.c ${GLAD}) add_executable(inputlag WIN32 MACOSX_BUNDLE inputlag.c ${GETOPT} ${GLAD}) add_executable(joysticks WIN32 MACOSX_BUNDLE joysticks.c ${GLAD}) +add_executable(opacity WIN32 MACOSX_BUNDLE opacity.c ${GLAD}) add_executable(tearing WIN32 MACOSX_BUNDLE tearing.c ${GETOPT} ${GLAD}) add_executable(threads WIN32 MACOSX_BUNDLE threads.c ${TINYCTHREAD} ${GLAD}) add_executable(timeout WIN32 MACOSX_BUNDLE timeout.c ${GLAD}) @@ -45,8 +46,8 @@ if (RT_LIBRARY) target_link_libraries(threads "${RT_LIBRARY}") endif() -set(WINDOWS_BINARIES empty gamma icon inputlag joysticks tearing threads - timeout title windows) +set(WINDOWS_BINARIES empty gamma icon inputlag joysticks opacity tearing + threads timeout title windows) set(CONSOLE_BINARIES clipboard events msaa glfwinfo iconify monitors reopen cursor) @@ -75,6 +76,7 @@ if (APPLE) set_target_properties(gamma PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Gamma") set_target_properties(inputlag PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Input Lag") set_target_properties(joysticks PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Joysticks") + set_target_properties(opacity PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Opacity") set_target_properties(tearing PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Tearing") set_target_properties(threads PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Threads") set_target_properties(timeout PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Timeout") diff --git a/tests/opacity.c b/tests/opacity.c new file mode 100644 index 00000000..3176c9ff --- /dev/null +++ b/tests/opacity.c @@ -0,0 +1,106 @@ +//======================================================================== +// Window opacity 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. +// +//======================================================================== + +#include +#include + +#define NK_IMPLEMENTATION +#define NK_INCLUDE_FIXED_TYPES +#define NK_INCLUDE_FONT_BAKING +#define NK_INCLUDE_DEFAULT_FONT +#define NK_INCLUDE_DEFAULT_ALLOCATOR +#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT +#define NK_INCLUDE_STANDARD_VARARGS +#include + +#define NK_GLFW_GL2_IMPLEMENTATION +#include + +#include +#include + +static void error_callback(int error, const char* description) +{ + fprintf(stderr, "Error: %s\n", description); +} + +int main(int argc, char** argv) +{ + GLFWmonitor* monitor = NULL; + GLFWwindow* window; + struct nk_context* nk; + struct nk_font_atlas* atlas; + + glfwSetErrorCallback(error_callback); + + if (!glfwInit()) + exit(EXIT_FAILURE); + + window = glfwCreateWindow(400, 400, "Opacity", NULL, NULL); + if (!window) + { + glfwTerminate(); + exit(EXIT_FAILURE); + } + + glfwMakeContextCurrent(window); + gladLoadGLLoader((GLADloadproc) glfwGetProcAddress); + glfwSwapInterval(1); + + nk = nk_glfw3_init(window, NK_GLFW3_INSTALL_CALLBACKS); + nk_glfw3_font_stash_begin(&atlas); + nk_glfw3_font_stash_end(); + + while (!glfwWindowShouldClose(window)) + { + int width, height; + struct nk_rect area; + + glfwGetWindowSize(window, &width, &height); + area = nk_rect(0.f, 0.f, (float) width, (float) height); + + glClear(GL_COLOR_BUFFER_BIT); + nk_glfw3_new_frame(); + if (nk_begin(nk, "", area, 0)) + { + float opacity = glfwGetWindowOpacity(window); + nk_layout_row_dynamic(nk, 30, 2); + if (nk_slider_float(nk, 0.f, &opacity, 1.f, 0.001f)) + glfwSetWindowOpacity(window, opacity); + nk_labelf(nk, NK_TEXT_LEFT, "%0.3f", opacity); + } + + nk_end(nk); + nk_glfw3_render(NK_ANTI_ALIASING_ON); + + glfwSwapBuffers(window); + glfwWaitEventsTimeout(1.0); + } + + nk_glfw3_shutdown(); + glfwTerminate(); + exit(EXIT_SUCCESS); +} +