diff --git a/lib/libimhex/include/hex/helpers/utils_macos.hpp b/lib/libimhex/include/hex/helpers/utils_macos.hpp index 83405a76f..8cb2caeb1 100644 --- a/lib/libimhex/include/hex/helpers/utils_macos.hpp +++ b/lib/libimhex/include/hex/helpers/utils_macos.hpp @@ -15,6 +15,9 @@ void setupMacosWindowStyle(GLFWwindow *window, bool borderlessWindowMode); void enumerateFontsMacos(); + + void macosHandleTitlebarDoubleClickGesture(GLFWwindow *window); + bool macosIsWindowBeingResizedByUser(GLFWwindow *window); } -#endif \ No newline at end of file +#endif diff --git a/lib/libimhex/source/helpers/utils_macos.m b/lib/libimhex/source/helpers/utils_macos.m index 686b3f022..82f9134ae 100644 --- a/lib/libimhex/source/helpers/utils_macos.m +++ b/lib/libimhex/source/helpers/utils_macos.m @@ -88,6 +88,36 @@ CFRelease(fontDescriptors); } + void macosHandleTitlebarDoubleClickGesture(GLFWwindow *window) { + NSWindow* cocoaWindow = glfwGetCocoaWindow(window); + + // Consult user preferences: "System Settings -> Desktop & Dock -> Double-click a window's title bar to" + NSString* action = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleActionOnDoubleClick"]; + + if (action == nil || [action isEqualToString:@"None"]) { + // Nothing to do + } else if ([action isEqualToString:@"Minimize"]) { + if ([cocoaWindow isMiniaturizable]) { + [cocoaWindow miniaturize:nil]; + } + } else if ([action isEqualToString:@"Maximize"]) { + // `[NSWindow zoom:_ sender]` takes over pumping the main runloop for the duration of the resize, + // and would interfere with our renderer's frame logic. Schedule it for the next frame + + CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{ + if ([cocoaWindow isZoomable]) { + [cocoaWindow zoom:nil]; + } + }); + } + } + + bool macosIsWindowBeingResizedByUser(GLFWwindow *window) { + NSWindow* cocoaWindow = glfwGetCocoaWindow(window); + + return cocoaWindow.inLiveResize; + } + @interface HexDocument : NSDocument @end diff --git a/main/gui/source/window/window.cpp b/main/gui/source/window/window.cpp index 204f16dfe..206aa5d30 100644 --- a/main/gui/source/window/window.cpp +++ b/main/gui/source/window/window.cpp @@ -683,14 +683,20 @@ namespace hex { glfwMakeContextCurrent(backupContext); if (shouldRender) { - int displayWidth, displayHeight; - glfwGetFramebufferSize(m_window, &displayWidth, &displayHeight); - glViewport(0, 0, displayWidth, displayHeight); - glClearColor(0.00F, 0.00F, 0.00F, 0.00F); - glClear(GL_COLOR_BUFFER_BIT); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + auto* drawData = ImGui::GetDrawData(); + + // Avoid accidentally clearing the viewport when the application is minimized, + // otherwise the OS will display an empty frame during deminimization on macOS + if (drawData->DisplaySize.x != 0 && drawData->DisplaySize.y != 0) { + int displayWidth, displayHeight; + glfwGetFramebufferSize(m_window, &displayWidth, &displayHeight); + glViewport(0, 0, displayWidth, displayHeight); + glClearColor(0.00F, 0.00F, 0.00F, 0.00F); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - glfwSwapBuffers(m_window); + glfwSwapBuffers(m_window); + } m_unlockFrameRate = true; } @@ -820,7 +826,12 @@ namespace hex { auto win = static_cast(glfwGetWindowUserPointer(window)); win->m_unlockFrameRate = true; - #if !defined(OS_MACOS) + #if defined(OS_MACOS) + // Stop widgets registering hover effects while the window is being resized + if (macosIsWindowBeingResizedByUser(window)) { + ImGui::GetIO().MousePos = ImVec2(); + } + #else win->fullFrame(); #endif }); diff --git a/plugins/builtin/source/content/window_decoration.cpp b/plugins/builtin/source/content/window_decoration.cpp index 9f1fbf405..148721084 100644 --- a/plugins/builtin/source/content/window_decoration.cpp +++ b/plugins/builtin/source/content/window_decoration.cpp @@ -392,6 +392,20 @@ namespace hex::plugin::builtin { drawMenu(); drawTitleBar(); + #if defined(OS_MACOS) + if (ImHexApi::System::isBorderlessWindowModeEnabled()) { + const auto windowSize = ImHexApi::System::getMainWindowSize(); + const auto menuUnderlaySize = ImVec2(windowSize.x, ImGui::GetCurrentWindowRead()->MenuBarHeight() * 1.5F); + + ImGui::SetCursorPos(ImVec2()); + + // Drawing this button late allows widgets rendered before it to grab click events, forming an "input underlay" + if (ImGui::InvisibleButton("##mainMenuUnderlay", menuUnderlaySize, ImGuiButtonFlags_PressedOnDoubleClick)) { + macosHandleTitlebarDoubleClickGesture(window); + } + } + #endif + ImGui::EndMainMenuBar(); } else { ImGui::PopStyleVar(2);