diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index d7f9174a..60898844 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -98,6 +98,7 @@ video tutorials. - IntellectualKitty - Aaron Jacobs - JannikGM + - Andreas O. Jansen - Erik S. V. Jansson - jjYBdx4IL - Toni Jovanoski diff --git a/docs/window.dox b/docs/window.dox index 6a51ac58..56a6d708 100644 --- a/docs/window.dox +++ b/docs/window.dox @@ -1161,7 +1161,7 @@ attention, the system will automatically end the request. @subsection window_taskbar_progress Window taskbar progress -If you whish to display the progress of some action on the taskbar, you can +If you wish to display the progress of some action on the taskbar, you can do this with @ref glfwSetWindowTaskbarProgress. @code diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index b7826263..39ebf7f5 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -1289,6 +1289,8 @@ extern "C" { * * @remark @x11 @wayland This behaves like @ref GLFW_TASKBAR_PROGRESS_NORMAL. * + * @remark @macos This displays a standard indeterminate `NSProgressIndicator`. + * * Used by @ref window_taskbar_progress. */ #define GLFW_TASKBAR_PROGRESS_INDETERMINATE 1 @@ -1305,7 +1307,7 @@ extern "C" { * * @remark @win32 This displays a red progress bar with 100% progress. * - * @remark @x11 @wayland This behaves like @ref GLFW_TASKBAR_PROGRESS_NORMAL. + * @remark @x11 @wayland @macos This behaves like @ref GLFW_TASKBAR_PROGRESS_NORMAL. * * Used by @ref window_taskbar_progress. */ @@ -1316,7 +1318,7 @@ extern "C" { * * @remark @win32 This displays a yellow filled progress bar. * - * @remark @x11 @wayland This behaves like @ref GLFW_TASKBAR_PROGRESS_NORMAL. + * @remark @x11 @wayland @macos This behaves like @ref GLFW_TASKBAR_PROGRESS_NORMAL. * * Used by @ref window_taskbar_progress. */ @@ -3374,7 +3376,8 @@ GLFWAPI void glfwSetWindowIcon(GLFWwindow* window, int count, const GLFWimage* i * * @remark @win32 On Windows Vista and earlier, this function will emit @ref GLFW_FEATURE_UNAVAILABLE. * - * @remark @macos This function will emit @ref GLFW_FEATURE_UNIMPLEMENTED. + * @remark @macos There exists only one Dock icon progress bar, and this + * displays the combined values of all the windows. * * @remark @x11 @wayland Requires a valid application desktop file with the same name * as the compiled executable. Due to limitations in the Unity Launcher API @@ -3391,7 +3394,7 @@ GLFWAPI void glfwSetWindowIcon(GLFWwindow* window, int count, const GLFWimage* i * * @ingroup window */ -GLFWAPI void glfwSetWindowTaskbarProgress(GLFWwindow* window, const int progressState, double value); +GLFWAPI void glfwSetWindowTaskbarProgress(GLFWwindow* window, int progressState, double value); /*! @brief Retrieves the position of the content area of the specified window. * diff --git a/src/cocoa_init.m b/src/cocoa_init.m index 2eff6a9a..5d199170 100644 --- a/src/cocoa_init.m +++ b/src/cocoa_init.m @@ -649,6 +649,12 @@ int _glfwInitCocoa(void) void _glfwTerminateCocoa(void) { @autoreleasepool { + + if (_glfw.ns.dockProgressIndicator.view != nil) + { + [_glfw.ns.dockProgressIndicator.view removeFromSuperview]; + [_glfw.ns.dockProgressIndicator.view release]; + } if (_glfw.ns.inputSource) { diff --git a/src/cocoa_platform.h b/src/cocoa_platform.h index 4323a874..aeda201b 100644 --- a/src/cocoa_platform.h +++ b/src/cocoa_platform.h @@ -156,6 +156,11 @@ typedef struct _GLFWwindowNS // since the last cursor motion event was processed // This is kept to counteract Cocoa doing the same internally double cursorWarpDeltaX, cursorWarpDeltaY; + + struct { + int state; + double value; + } dockProgressIndicator; } _GLFWwindowNS; // Cocoa-specific global data @@ -189,6 +194,13 @@ typedef struct _GLFWlibraryNS PFN_LMGetKbdType GetKbdType; CFStringRef kPropertyUnicodeKeyLayoutData; } tis; + + struct { + id view; + int windowCount; + int indeterminateCount; + double totalValue; + } dockProgressIndicator; } _GLFWlibraryNS; // Cocoa-specific per-monitor data @@ -218,7 +230,7 @@ GLFWbool _glfwCreateWindowCocoa(_GLFWwindow* window, const _GLFWwndconfig* wndco void _glfwDestroyWindowCocoa(_GLFWwindow* window); void _glfwSetWindowTitleCocoa(_GLFWwindow* window, const char* title); void _glfwSetWindowIconCocoa(_GLFWwindow* window, int count, const GLFWimage* images); -void _glfwSetWindowTaskbarProgressCocoa(_GLFWwindow* window, const int taskbarState, double value); +void _glfwSetWindowTaskbarProgressCocoa(_GLFWwindow* window, int progressState, double value); void _glfwGetWindowPosCocoa(_GLFWwindow* window, int* xpos, int* ypos); void _glfwSetWindowPosCocoa(_GLFWwindow* window, int xpos, int ypos); void _glfwGetWindowSizeCocoa(_GLFWwindow* window, int* width, int* height); diff --git a/src/cocoa_window.m b/src/cocoa_window.m index a7ba3030..7ec251e0 100644 --- a/src/cocoa_window.m +++ b/src/cocoa_window.m @@ -197,6 +197,83 @@ static NSUInteger translateKeyToModifierFlag(int key) // static const NSRange kEmptyRange = { NSNotFound, 0 }; +static NSProgressIndicator* createProgressIndicator(const NSDockTile* dockTile) +{ + NSView* contentView = [dockTile contentView]; + + NSProgressIndicator* indicator = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(0.0f, 0.0f, contentView.frame.size.width, 15.0f)]; + + [indicator setStyle:NSProgressIndicatorStyleBar]; + + if (@available(macOS 11.0, *)) + { + [indicator setControlSize:NSControlSizeLarge]; + } + + [indicator setMinValue:0.0f]; + [indicator setMaxValue:1.0f]; + + [indicator sizeToFit]; + + [contentView addSubview:indicator]; + + _glfw.ns.dockProgressIndicator.view = indicator; + + return indicator; +} + +static void setDockProgressIndicator(int progressState, double value) +{ + NSProgressIndicator* indicator = _glfw.ns.dockProgressIndicator.view; + + NSDockTile* dockTile = [[NSApplication sharedApplication] dockTile]; + + if (indicator == nil) + { + if ([dockTile contentView] == nil) + { + NSImageView *iconView = [[NSImageView alloc] init]; + [iconView setImage:[[NSApplication sharedApplication] applicationIconImage]]; + [dockTile setContentView:iconView]; + [iconView release]; + } + + indicator = createProgressIndicator(dockTile); + } + + // ### Switching from INDETERMINATE to NORMAL, PAUSED or ERROR requires 2 invocations in different frames. + // In MacOS 12 (and probably other versions), an indeterminate progress bar is rendered as a normal bar + // with 0.0 progress. So when calling [progressIndicator setIndeterminate:YES], the indicator actually + // sets its doubleValue to 0.0. + // The bug is caused by NSProgressIndicator not immediately updating its value when it's increasing. + // This code illustrates the exact same problem, but this time from NORMAL, PAUSED and ERROR to INDETERMINATE: + // + // if (progressState == GLFW_TASKBAR_PROGRESS_INDETERMINATE) + // [progressIndicator setDoubleValue:0.75]; + // else + // [progressIndicator setDoubleValue:0.25]; + // + // This is likely a bug in Cocoa. + // + // ### Progress increments are delayed + // What this also means, is that each time the progress increments, the bar's progress will be 1 frame delayed, + // and only updated once a higher or similar value is again set the next frame. + + // Workaround for the aforementioned issues. If there's any versions of MacOS where + // this issue is not present, this should be ommitted in those versions. + if ([indicator isIndeterminate] || [indicator doubleValue] < value) + { + [indicator removeFromSuperview]; + [indicator release]; + indicator = createProgressIndicator(dockTile); + } + + [indicator setIndeterminate:progressState == GLFW_TASKBAR_PROGRESS_INDETERMINATE]; + [indicator setHidden:progressState == GLFW_TASKBAR_PROGRESS_DISABLED]; + [indicator setDoubleValue:value]; + + [dockTile display]; +} //------------------------------------------------------------------------ // Delegate for window related notifications @@ -986,6 +1063,8 @@ GLFWbool _glfwCreateWindowCocoa(_GLFWwindow* window, void _glfwDestroyWindowCocoa(_GLFWwindow* window) { @autoreleasepool { + + _glfwSetWindowTaskbarProgressCocoa(window, GLFW_TASKBAR_PROGRESS_DISABLED, 0.0); if (_glfw.ns.disabledCursorWindow == window) _glfw.ns.disabledCursorWindow = NULL; @@ -1032,10 +1111,60 @@ void _glfwSetWindowIconCocoa(_GLFWwindow* window, "Cocoa: Regular windows do not have icons on macOS"); } -void _glfwSetWindowTaskbarProgressCocoa(_GLFWwindow* window, const int progressState, double value) +void _glfwSetWindowTaskbarProgressCocoa(_GLFWwindow* window, int progressState, double value) { - _glfwInputError(GLFW_FEATURE_UNIMPLEMENTED, - "Cocoa: Window taskbar progress is not implemented"); + if (progressState == GLFW_TASKBAR_PROGRESS_ERROR || progressState == GLFW_TASKBAR_PROGRESS_PAUSED) + progressState = GLFW_TASKBAR_PROGRESS_NORMAL; + + const int oldState = window->ns.dockProgressIndicator.state; + const int state = progressState; + + const double oldValue = window->ns.dockProgressIndicator.value; + + if (oldState == state) + { + if (state == GLFW_TASKBAR_PROGRESS_DISABLED || + state == GLFW_TASKBAR_PROGRESS_INDETERMINATE || + oldValue == value) + return; + } + + if (oldState != state) + { + // Reset + if (oldState == GLFW_TASKBAR_PROGRESS_INDETERMINATE) + --_glfw.ns.dockProgressIndicator.indeterminateCount; + if (oldState != GLFW_TASKBAR_PROGRESS_DISABLED) + { + --_glfw.ns.dockProgressIndicator.windowCount; + _glfw.ns.dockProgressIndicator.totalValue -= oldValue; + } + + // Set + if (state == GLFW_TASKBAR_PROGRESS_INDETERMINATE) + ++_glfw.ns.dockProgressIndicator.indeterminateCount; + if (state != GLFW_TASKBAR_PROGRESS_DISABLED) + { + ++_glfw.ns.dockProgressIndicator.windowCount; + _glfw.ns.dockProgressIndicator.totalValue += value; + } + } + else if (state != GLFW_TASKBAR_PROGRESS_DISABLED) + _glfw.ns.dockProgressIndicator.totalValue += (value - oldValue); + + + if (_glfw.ns.dockProgressIndicator.windowCount > _glfw.ns.dockProgressIndicator.indeterminateCount) + { + const double finalValue = _glfw.ns.dockProgressIndicator.totalValue / _glfw.ns.dockProgressIndicator.windowCount; + setDockProgressIndicator(GLFW_TASKBAR_PROGRESS_NORMAL, finalValue); + } + else if (_glfw.ns.dockProgressIndicator.indeterminateCount > 0) + setDockProgressIndicator(GLFW_TASKBAR_PROGRESS_INDETERMINATE, 0.0f); + else + setDockProgressIndicator(GLFW_TASKBAR_PROGRESS_DISABLED, 0.0f); + + window->ns.dockProgressIndicator.state = state; + window->ns.dockProgressIndicator.value = value; } void _glfwGetWindowPosCocoa(_GLFWwindow* window, int* xpos, int* ypos) diff --git a/src/null_platform.h b/src/null_platform.h index a4e5e4ad..c430eb37 100644 --- a/src/null_platform.h +++ b/src/null_platform.h @@ -89,7 +89,7 @@ GLFWbool _glfwCreateWindowNull(_GLFWwindow* window, const _GLFWwndconfig* wndcon void _glfwDestroyWindowNull(_GLFWwindow* window); void _glfwSetWindowTitleNull(_GLFWwindow* window, const char* title); void _glfwSetWindowIconNull(_GLFWwindow* window, int count, const GLFWimage* images); -void _glfwSetWindowTaskbarProgressNull(_GLFWwindow* window, const int taskbarState, double value); +void _glfwSetWindowTaskbarProgressNull(_GLFWwindow* window, int progressState, double value); void _glfwSetWindowMonitorNull(_GLFWwindow* window, _GLFWmonitor* monitor, int xpos, int ypos, int width, int height, int refreshRate); void _glfwGetWindowPosNull(_GLFWwindow* window, int* xpos, int* ypos); void _glfwSetWindowPosNull(_GLFWwindow* window, int xpos, int ypos); diff --git a/src/null_window.c b/src/null_window.c index 2638827e..812c7ab3 100644 --- a/src/null_window.c +++ b/src/null_window.c @@ -187,7 +187,7 @@ void _glfwSetWindowIconNull(_GLFWwindow* window, int count, const GLFWimage* ima { } -void _glfwSetWindowTaskbarProgressNull(_GLFWwindow* window, const int progressState, double value) +void _glfwSetWindowTaskbarProgressNull(_GLFWwindow* window, int progressState, double value) { } diff --git a/src/win32_platform.h b/src/win32_platform.h index 34847e81..b84d46a3 100644 --- a/src/win32_platform.h +++ b/src/win32_platform.h @@ -618,7 +618,7 @@ GLFWbool _glfwCreateWindowWin32(_GLFWwindow* window, const _GLFWwndconfig* wndco void _glfwDestroyWindowWin32(_GLFWwindow* window); void _glfwSetWindowTitleWin32(_GLFWwindow* window, const char* title); void _glfwSetWindowIconWin32(_GLFWwindow* window, int count, const GLFWimage* images); -void _glfwSetWindowTaskbarProgressWin32(_GLFWwindow* window, const int taskbarState, double value); +void _glfwSetWindowTaskbarProgressWin32(_GLFWwindow* window, int progressState, double value); void _glfwGetWindowPosWin32(_GLFWwindow* window, int* xpos, int* ypos); void _glfwSetWindowPosWin32(_GLFWwindow* window, int xpos, int ypos); void _glfwGetWindowSizeWin32(_GLFWwindow* window, int* width, int* height); diff --git a/src/win32_window.c b/src/win32_window.c index 44a2ae00..e68d2839 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -1596,7 +1596,7 @@ void _glfwSetWindowIconWin32(_GLFWwindow* window, int count, const GLFWimage* im } } -void _glfwSetWindowTaskbarProgressWin32(_GLFWwindow* window, const int progressState, double value) +void _glfwSetWindowTaskbarProgressWin32(_GLFWwindow* window, int progressState, double value) { HRESULT res = S_OK; int winProgressState = 0; diff --git a/src/window.c b/src/window.c index c68b7fd6..c7aca29d 100644 --- a/src/window.c +++ b/src/window.c @@ -558,7 +558,7 @@ GLFWAPI void glfwSetWindowIcon(GLFWwindow* handle, _glfw.platform.setWindowIcon(window, count, images); } -GLFWAPI void glfwSetWindowTaskbarProgress(GLFWwindow* handle, const int progressState, double value) +GLFWAPI void glfwSetWindowTaskbarProgress(GLFWwindow* handle, int progressState, double value) { _GLFWwindow* window = (_GLFWwindow*) handle; diff --git a/src/wl_platform.h b/src/wl_platform.h index fabc90a2..918f232d 100644 --- a/src/wl_platform.h +++ b/src/wl_platform.h @@ -446,7 +446,7 @@ GLFWbool _glfwCreateWindowWayland(_GLFWwindow* window, const _GLFWwndconfig* wnd void _glfwDestroyWindowWayland(_GLFWwindow* window); void _glfwSetWindowTitleWayland(_GLFWwindow* window, const char* title); void _glfwSetWindowIconWayland(_GLFWwindow* window, int count, const GLFWimage* images); -void _glfwSetWindowTaskbarProgressWayland(_GLFWwindow* window, const int taskbarState, double value); +void _glfwSetWindowTaskbarProgressWayland(_GLFWwindow* window, int progressState, double value); void _glfwGetWindowPosWayland(_GLFWwindow* window, int* xpos, int* ypos); void _glfwSetWindowPosWayland(_GLFWwindow* window, int xpos, int ypos); void _glfwGetWindowSizeWayland(_GLFWwindow* window, int* width, int* height); diff --git a/src/wl_window.c b/src/wl_window.c index f35072d5..a4460e93 100644 --- a/src/wl_window.c +++ b/src/wl_window.c @@ -1902,11 +1902,11 @@ void _glfwSetWindowIconWayland(_GLFWwindow* window, "Wayland: The platform does not support setting the window icon"); } -void _glfwSetWindowTaskbarProgressWayland(_GLFWwindow* window, const int taskbarState, double value) +void _glfwSetWindowTaskbarProgressWayland(_GLFWwindow* window, const int progressState, double value) { (void)window; - const dbus_bool_t progressVisible = (taskbarState != GLFW_TASKBAR_PROGRESS_DISABLED); + const dbus_bool_t progressVisible = (progressState != GLFW_TASKBAR_PROGRESS_DISABLED); _glfwUpdateTaskbarProgressDBusPOSIX(progressVisible, value); } diff --git a/src/x11_platform.h b/src/x11_platform.h index 6eeeab4e..96280653 100644 --- a/src/x11_platform.h +++ b/src/x11_platform.h @@ -905,7 +905,7 @@ GLFWbool _glfwCreateWindowX11(_GLFWwindow* window, const _GLFWwndconfig* wndconf void _glfwDestroyWindowX11(_GLFWwindow* window); void _glfwSetWindowTitleX11(_GLFWwindow* window, const char* title); void _glfwSetWindowIconX11(_GLFWwindow* window, int count, const GLFWimage* images); -void _glfwSetWindowTaskbarProgressX11(_GLFWwindow* window, const int taskbarState, double value); +void _glfwSetWindowTaskbarProgressX11(_GLFWwindow* window, int progressState, double value); void _glfwGetWindowPosX11(_GLFWwindow* window, int* xpos, int* ypos); void _glfwSetWindowPosX11(_GLFWwindow* window, int xpos, int ypos); void _glfwGetWindowSizeX11(_GLFWwindow* window, int* width, int* height); diff --git a/src/x11_window.c b/src/x11_window.c index 236ecc45..6c2297ef 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -2152,11 +2152,11 @@ void _glfwSetWindowIconX11(_GLFWwindow* window, int count, const GLFWimage* imag XFlush(_glfw.x11.display); } -void _glfwSetWindowTaskbarProgressX11(_GLFWwindow* window, const int taskbarState, double value) +void _glfwSetWindowTaskbarProgressX11(_GLFWwindow* window, int progressState, double value) { (void)window; - const dbus_bool_t progressVisible = (taskbarState != GLFW_TASKBAR_PROGRESS_DISABLED); + const dbus_bool_t progressVisible = (progressState != GLFW_TASKBAR_PROGRESS_DISABLED); _glfwUpdateTaskbarProgressDBusPOSIX(progressVisible, value); } diff --git a/tests/window.c b/tests/window.c index 69975aee..f36a1dda 100644 --- a/tests/window.c +++ b/tests/window.c @@ -418,20 +418,22 @@ int main(int argc, char** argv) nk_layout_row_dynamic(nk, 30, 5); + static int state = GLFW_TASKBAR_PROGRESS_DISABLED; static float progress = 0; if(nk_button_label(nk, "No progress")) - glfwSetWindowTaskbarProgress(window, GLFW_TASKBAR_PROGRESS_DISABLED, (double)progress); + glfwSetWindowTaskbarProgress(window, state = GLFW_TASKBAR_PROGRESS_DISABLED, (double) progress); if (nk_button_label(nk, "Indeterminate")) - glfwSetWindowTaskbarProgress(window, GLFW_TASKBAR_PROGRESS_INDETERMINATE, (double)progress); + glfwSetWindowTaskbarProgress(window, state = GLFW_TASKBAR_PROGRESS_INDETERMINATE, (double) progress); if (nk_button_label(nk, "Normal")) - glfwSetWindowTaskbarProgress(window, GLFW_TASKBAR_PROGRESS_NORMAL, (double)progress); + glfwSetWindowTaskbarProgress(window, state = GLFW_TASKBAR_PROGRESS_NORMAL, (double) progress); if (nk_button_label(nk, "Error")) - glfwSetWindowTaskbarProgress(window, GLFW_TASKBAR_PROGRESS_ERROR, (double)progress); + glfwSetWindowTaskbarProgress(window, state = GLFW_TASKBAR_PROGRESS_ERROR, (double) progress); if (nk_button_label(nk, "Paused")) - glfwSetWindowTaskbarProgress(window, GLFW_TASKBAR_PROGRESS_PAUSED, (double)progress); + glfwSetWindowTaskbarProgress(window, state = GLFW_TASKBAR_PROGRESS_PAUSED, (double) progress); nk_label(nk, "Progress: ", NK_TEXT_ALIGN_LEFT); - nk_slider_float(nk, 0.0f, &progress, 1.0f, 0.05f); + if (nk_slider_float(nk, 0.0f, &progress, 1.0f, 0.05f)) + glfwSetWindowTaskbarProgress(window, state, (double) progress); } nk_end(nk);