From 20b7afd97d54ac53da842ef554524e98897236aa Mon Sep 17 00:00:00 2001 From: kunitoki Date: Thu, 2 Jul 2026 08:34:11 +0200 Subject: [PATCH 1/5] Fix VST3 windows --- .../native/yup_GraphicsContext_d3d.cpp | 34 +++++- modules/yup_gui/native/yup_Windowing_sdl2.cpp | 113 +++++++++++++++--- 2 files changed, 125 insertions(+), 22 deletions(-) diff --git a/modules/yup_graphics/native/yup_GraphicsContext_d3d.cpp b/modules/yup_graphics/native/yup_GraphicsContext_d3d.cpp index 6d563ceb8..fda923811 100644 --- a/modules/yup_graphics/native/yup_GraphicsContext_d3d.cpp +++ b/modules/yup_graphics/native/yup_GraphicsContext_d3d.cpp @@ -68,6 +68,9 @@ class LowLevelRenderContextD3D : public GraphicsContext scd.BufferCount = 2; scd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; + fprintf (stderr, "D3D: CreateSwapChainForHwnd hwnd=%p size=%dx%d\n", window, width, height); + fflush (stderr); + VERIFY_OK (m_d3dFactory->CreateSwapChainForHwnd (m_gpu.Get(), (HWND) window, &scd, @@ -117,9 +120,18 @@ class LowLevelRenderContextD3D : public GraphicsContext else { ComPtr backbuffer; - VERIFY_OK (m_swapchain->GetBuffer (0, - __uuidof (ID3D11Texture2D), - reinterpret_cast (backbuffer.ReleaseAndGetAddressOf()))); + HRESULT hr = m_swapchain->GetBuffer (0, + __uuidof (ID3D11Texture2D), + reinterpret_cast (backbuffer.ReleaseAndGetAddressOf())); + + if (FAILED (hr)) + { + auto reason = m_gpu->GetDeviceRemovedReason(); + fprintf (stderr, "D3D: GetBuffer failed: hr=0x%08X, deviceRemovedReason=0x%08X\n", static_cast (hr), static_cast (reason)); + fflush (stderr); + m_renderTarget->setTargetTexture (nullptr); + return; + } m_renderTarget->setTargetTexture (backbuffer); } @@ -130,7 +142,21 @@ class LowLevelRenderContextD3D : public GraphicsContext m_renderContext->flush (flushDesc); if (! m_isHeadless) - m_swapchain->Present (0, 0); + { + HRESULT hr = m_swapchain->Present (0, 0); + + if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) + { + auto reason = m_gpu->GetDeviceRemovedReason(); + fprintf (stderr, "D3D: Present returned device removed/reset: hr=0x%08X, deviceRemovedReason=0x%08X\n", static_cast (hr), static_cast (reason)); + fflush (stderr); + } + else if (FAILED (hr)) + { + fprintf (stderr, "D3D: Present failed: hr=0x%08X\n", static_cast (hr)); + fflush (stderr); + } + } m_renderTarget->setTargetTexture (nullptr); } diff --git a/modules/yup_gui/native/yup_Windowing_sdl2.cpp b/modules/yup_gui/native/yup_Windowing_sdl2.cpp index 0546116e9..7feec6a75 100644 --- a/modules/yup_gui/native/yup_Windowing_sdl2.cpp +++ b/modules/yup_gui/native/yup_Windowing_sdl2.cpp @@ -94,27 +94,71 @@ SDL2ComponentNative::SDL2ComponentNative (Component& component, // Create the window, renderer and parent it YUP_MODULE_DBG (GUI_WINDOWING, "SDL2: creating window: title=" << component.getTitle() << ", flags=" << String::toHexString (static_cast (windowFlags)) << ", parent=" << String::toHexString (static_cast (reinterpret_cast (parent)))); - window = SDL_CreateWindow (component.getTitle().toRawUTF8(), - SDL_WINDOWPOS_UNDEFINED, - SDL_WINDOWPOS_UNDEFINED, - 1, - 1, - windowFlags); - if (window == nullptr) +#if YUP_WINDOWS + if (parent != nullptr) { - YUP_MODULE_DBG (GUI_WINDOWING, "SDL2: unable to create heavyweight window: " << SDL_GetError()); - return; // TODO - raise something ? + DWORD style = WS_CHILDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS; + + if (options.flags.test (resizableWindow)) + style |= WS_THICKFRAME | WS_MAXIMIZEBOX; + + if (component.isVisible()) + style |= WS_VISIBLE; + + HWND childHwnd = CreateWindowExW (0, + L"SDL_app", + component.getTitle().toWideCharPointer(), + style, + 0, 0, + jmax (1, screenBounds.getWidth()), + jmax (1, screenBounds.getHeight()), + reinterpret_cast (parent), + nullptr, + GetModuleHandleW (nullptr), + nullptr); + + if (childHwnd == nullptr) + { + YUP_MODULE_DBG (GUI_WINDOWING, "SDL2: unable to create child window"); + return; + } + + window = SDL_CreateWindowFrom (reinterpret_cast (childHwnd)); + + if (window == nullptr) + { + YUP_MODULE_DBG (GUI_WINDOWING, "SDL2: unable to wrap child window with SDL: " << SDL_GetError()); + DestroyWindow (childHwnd); + return; + } + } + else +#endif + { + window = SDL_CreateWindow (component.getTitle().toRawUTF8(), + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + 1, + 1, + windowFlags); + if (window == nullptr) + { + YUP_MODULE_DBG (GUI_WINDOWING, "SDL2: unable to create heavyweight window: " << SDL_GetError()); + return; // TODO - raise something ? + } } YUP_MODULE_DBG (GUI_WINDOWING, "SDL2: created window: id=" << static_cast (SDL_GetWindowID (window)) << ", window=" << String::toHexString (static_cast (reinterpret_cast (window)))); SDL_SetWindowData (window, "self", this); +#if ! YUP_WINDOWS if (parent != nullptr) { setNativeParent (parent, window); YUP_MODULE_DBG (GUI_WINDOWING, "SDL2: set native parent"); } +#endif if (currentGraphicsApi == GraphicsContext::OpenGL) { @@ -739,6 +783,18 @@ void SDL2ComponentNative::handleAsyncUpdate() void SDL2ComponentNative::timerCallback() { +#if YUP_WINDOWS + if (currentMouseButtons != MouseEvent::noButtons && window != nullptr) + { + POINT cursorPos; + if (GetCursorPos (&cursorPos)) + { + ScreenToClient (reinterpret_cast (getNativeHandle()), &cursorPos); + handleMouseMoveOrDrag (Point { static_cast (cursorPos.x), static_cast (cursorPos.y) }); + } + } +#endif + renderContext(); } @@ -1005,6 +1061,10 @@ void SDL2ComponentNative::handleMouseDown (const Point& position, MouseEv lastMouseDownPosition = position; lastMouseDownTime = currentMouseDownTime; + +#if YUP_WINDOWS + SetCapture (reinterpret_cast (getNativeHandle())); +#endif } lastMouseMovePosition = position; @@ -1053,6 +1113,10 @@ void SDL2ComponentNative::handleMouseUp (const Point& position, MouseEven if (currentMouseButtons == MouseEvent::noButtons) { +#if YUP_WINDOWS + ReleaseCapture(); +#endif + updateComponentUnderMouse (event); lastComponentClicked = nullptr; @@ -1101,6 +1165,9 @@ void SDL2ComponentNative::handleMouseWheel (const Point& position, const void SDL2ComponentNative::handleMouseEnter (const Point& position) { + if (currentMouseButtons != MouseEvent::noButtons) + return; + auto event = MouseEvent() .withButtons (currentMouseButtons) .withModifiers (currentKeyModifiers) @@ -1118,6 +1185,9 @@ void SDL2ComponentNative::handleMouseEnter (const Point& position) void SDL2ComponentNative::handleMouseLeave (const Point& position) { + if (currentMouseButtons != MouseEvent::noButtons) + return; + auto event = MouseEvent() .withButtons (currentMouseButtons) .withModifiers (currentKeyModifiers) @@ -1179,17 +1249,22 @@ void SDL2ComponentNative::handleMoved (int xpos, int ypos) YUP_MODULE_DBG (GUI_WINDOWING, "SDL2: handleMoved " << screenBounds.getX() << " " << screenBounds.getY() << " -> " << xpos << " " << ypos << ", parent=" << String::toHexString (static_cast (reinterpret_cast (parentWindow)))); - component.internalMoved (xpos, ypos); - - screenBounds = screenBounds.withPosition (xpos, ypos); + if (context == nullptr) + return; if (parentWindow != nullptr) { auto preventBoundsChange = ScopedValueSetter (internalBoundsChange, true); - auto nativeWindowPos = getNativeWindowPosition (parentWindow); - YUP_MODULE_DBG (GUI_WINDOWING, "SDL2: parent window position sync after move: " << nativeWindowPos.toString()); - setPosition (nativeWindowPos.getTopLeft()); + YUP_MODULE_DBG (GUI_WINDOWING, "SDL2: parent window position sync after move: resetting to parent-relative (0, 0)"); + setPosition ({ 0, 0 }); + component.internalMoved (0, 0); + } + else + { + component.internalMoved (xpos, ypos); + + screenBounds = screenBounds.withPosition (xpos, ypos); } } @@ -1199,6 +1274,9 @@ void SDL2ComponentNative::handleResized (int width, int height) YUP_MODULE_DBG (GUI_WINDOWING, "SDL2: handleResized " << screenBounds.getWidth() << "x" << screenBounds.getHeight() << " -> " << width << "x" << height << ", parent=" << String::toHexString (static_cast (reinterpret_cast (parentWindow)))); + if (context == nullptr) + return; + component.internalResized (width, height); screenBounds = screenBounds.withSize (width, height); @@ -1207,9 +1285,8 @@ void SDL2ComponentNative::handleResized (int width, int height) { auto preventBoundsChange = ScopedValueSetter (internalBoundsChange, true); - auto nativeWindowPos = getNativeWindowPosition (parentWindow); - YUP_MODULE_DBG (GUI_WINDOWING, "SDL2: parent window position sync after resize: " << nativeWindowPos.toString()); - setPosition (nativeWindowPos.getTopLeft()); + YUP_MODULE_DBG (GUI_WINDOWING, "SDL2: parent window position sync after resize: resetting to parent-relative (0, 0)"); + setPosition ({ 0, 0 }); } if (dynamic_cast (&component) == nullptr) From 788c7a720aca680a34433168e8a63079ffa7ed03 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Thu, 2 Jul 2026 09:52:59 +0200 Subject: [PATCH 2/5] More windows fixes --- modules/yup_core/native/yup_Files_windows.cpp | 6 ++++ modules/yup_gui/native/yup_Windowing_sdl2.cpp | 28 ++++++++++++++++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/modules/yup_core/native/yup_Files_windows.cpp b/modules/yup_core/native/yup_Files_windows.cpp index aa0c6fefb..a8b4d9d11 100644 --- a/modules/yup_core/native/yup_Files_windows.cpp +++ b/modules/yup_core/native/yup_Files_windows.cpp @@ -91,6 +91,12 @@ YUP_END_IGNORE_WARNINGS_GCC_LIKE //============================================================================== yup::String toLongPath (const yup::String& path) { + if (path.startsWith (String (L"\\\\?\\"))) + return path; + + if (path.startsWith (String (L"\\\\"))) + return L"\\\\?\\UNC" + path.substring (1); + return L"\\\\?\\" + path; } diff --git a/modules/yup_gui/native/yup_Windowing_sdl2.cpp b/modules/yup_gui/native/yup_Windowing_sdl2.cpp index 7feec6a75..4df2ab726 100644 --- a/modules/yup_gui/native/yup_Windowing_sdl2.cpp +++ b/modules/yup_gui/native/yup_Windowing_sdl2.cpp @@ -97,16 +97,32 @@ SDL2ComponentNative::SDL2ComponentNative (Component& component, #if YUP_WINDOWS if (parent != nullptr) { + // Register a plain window class to avoid triggering SDL's WndProc during creation + // (SDL_CreateWindowFrom will subclass it afterwards). + static const wchar_t childWindowClass[] = L"YUPChildWindow"; + static bool childWindowClassRegistered = false; + + if (! childWindowClassRegistered) + { + WNDCLASSEXW wc = {}; + wc.cbSize = sizeof (WNDCLASSEXW); + wc.lpfnWndProc = DefWindowProcW; + wc.hInstance = GetModuleHandleW (nullptr); + wc.hCursor = LoadCursorW (nullptr, IDC_ARROW); + wc.lpszClassName = childWindowClass; + childWindowClassRegistered = RegisterClassExW (&wc) != 0; + } + DWORD style = WS_CHILDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS; - if (options.flags.test (resizableWindow)) - style |= WS_THICKFRAME | WS_MAXIMIZEBOX; + if (options.flags.test (decoratedWindow)) + style |= WS_CAPTION; if (component.isVisible()) style |= WS_VISIBLE; HWND childHwnd = CreateWindowExW (0, - L"SDL_app", + childWindowClass, component.getTitle().toWideCharPointer(), style, 0, 0, @@ -790,7 +806,11 @@ void SDL2ComponentNative::timerCallback() if (GetCursorPos (&cursorPos)) { ScreenToClient (reinterpret_cast (getNativeHandle()), &cursorPos); - handleMouseMoveOrDrag (Point { static_cast (cursorPos.x), static_cast (cursorPos.y) }); + + auto cursorPosition = Point { static_cast (cursorPos.x), static_cast (cursorPos.y) }; + + if (lastMouseMovePosition != cursorPosition) + handleMouseMoveOrDrag (cursorPosition); } } #endif From 80595dac80e62c539da36d77cb2e5fe1410aa07f Mon Sep 17 00:00:00 2001 From: kunitoki Date: Thu, 2 Jul 2026 11:39:37 +0200 Subject: [PATCH 3/5] Fix popup menu --- modules/yup_gui/menus/yup_PopupMenu.cpp | 62 +++++++++++++++++-- modules/yup_gui/native/yup_Windowing_sdl2.cpp | 1 - modules/yup_gui/yup_gui.h | 10 +++ 3 files changed, 66 insertions(+), 7 deletions(-) diff --git a/modules/yup_gui/menus/yup_PopupMenu.cpp b/modules/yup_gui/menus/yup_PopupMenu.cpp index 8c498da7f..3894b576f 100644 --- a/modules/yup_gui/menus/yup_PopupMenu.cpp +++ b/modules/yup_gui/menus/yup_PopupMenu.cpp @@ -121,7 +121,10 @@ void installGlobalMouseListener() { const auto globalPos = event.getScreenPosition(); if (auto* popupMenu = findActivePopupAt (globalPos)) + { + YUP_MODULE_DBG (GUI_POPUPMENU, "GlobalMouseListener::mouseMove: dispatching to popup=" << String::toHexString (reinterpret_cast (popupMenu)) << " globalPos=" << globalPos.toString()); popupMenu->mouseMove (makePopupMouseEvent (event, *popupMenu, globalPos)); + } } } globalMouseListener {}; @@ -457,6 +460,8 @@ PopupMenu::Ptr PopupMenu::create (const Options& options) void PopupMenu::dismissAllPopups() { + YUP_MODULE_DBG (GUI_POPUPMENU, "dismissAllPopups: count=" << activePopups.size()); + auto popupsToClose = std::exchange (activePopups, {}); for (const auto& popup : popupsToClose) @@ -721,6 +726,8 @@ void PopupMenu::show (std::function callback) void PopupMenu::showCustom (const Options& options, bool isSubmenu, std::function callback) { + YUP_MODULE_DBG (GUI_POPUPMENU, "showCustom: isSubmenu=" << (isSubmenu ? 1 : 0) << " isOnDesktop=" << (isOnDesktop() ? 1 : 0) << " numItems=" << getNumItems() << " [popup=" << String::toHexString (reinterpret_cast (this)) << "]"); + if (! isSubmenu) dismissAllPopups(); @@ -755,7 +762,10 @@ void PopupMenu::showCustom (const Options& options, bool isSubmenu, std::functio .withMouseCapture (true); if (! isOnDesktop()) + { + YUP_MODULE_DBG (GUI_POPUPMENU, "showCustom: adding to desktop [popup=" << String::toHexString (reinterpret_cast (this)) << "]"); addToDesktop (nativeOptions); + } } removeActivePopup (this); @@ -766,6 +776,8 @@ void PopupMenu::showCustom (const Options& options, bool isSubmenu, std::functio setVisible (true); toFront (true); + + YUP_MODULE_DBG (GUI_POPUPMENU, "showCustom EXIT: visible=" << (isVisible() ? 1 : 0) << " size=" << getWidth() << "x" << getHeight() << " screenBounds=" << getScreenBounds().toString() << " [popup=" << String::toHexString (reinterpret_cast (this)) << "]"); } //============================================================================== @@ -780,6 +792,8 @@ void PopupMenu::dismiss (int itemID) if (isBeingDismissed) return; + YUP_MODULE_DBG (GUI_POPUPMENU, "dismiss: itemID=" << itemID << " isOnDesktop=" << (isOnDesktop() ? 1 : 0) << " [popup=" << String::toHexString (reinterpret_cast (this)) << "]"); + isBeingDismissed = true; hideSubmenus(); @@ -898,6 +912,8 @@ void PopupMenu::mouseMove (const MouseEvent& event) if (itemIndex >= 0 && isItemSelectable (itemIndex)) { + YUP_MODULE_DBG (GUI_POPUPMENU, "mouseMove: itemIndex=" << itemIndex << " (" << items[itemIndex]->text << ") [popup=" << String::toHexString (reinterpret_cast (this)) << "]"); + // Set selection on hover for selectable items setSelectedItemIndex (itemIndex, true); @@ -917,6 +933,8 @@ void PopupMenu::mouseMove (const MouseEvent& event) } else if (itemIndex < 0) { + YUP_MODULE_DBG (GUI_POPUPMENU, "mouseMove: itemIndex=" << itemIndex << " (no item) [popup=" << String::toHexString (reinterpret_cast (this)) << "]"); + // Mouse is not over any item, clear selection setSelectedItemIndex (-1, true); } @@ -925,18 +943,24 @@ void PopupMenu::mouseMove (const MouseEvent& event) void PopupMenu::mouseEnter (const MouseEvent& event) { int itemIndex = getItemIndexAt (event.getPosition()); + YUP_MODULE_DBG (GUI_POPUPMENU, "mouseEnter: itemIndex=" << itemIndex << " hasVisibleSubmenu=" << (hasVisibleSubmenu() ? 1 : 0) << " [popup=" << String::toHexString (reinterpret_cast (this)) << "]"); + if (itemIndex >= 0 && isItemSelectable (itemIndex)) { setSelectedItemIndex (itemIndex, true); - - auto& item = *items[itemIndex]; - if (item.isSubMenu() && item.isEnabled) - showSubmenu (itemIndex); } } void PopupMenu::mouseExit (const MouseEvent& event) { + YUP_MODULE_DBG (GUI_POPUPMENU, "mouseExit: hasVisibleSubmenu=" << (hasVisibleSubmenu() ? 1 : 0) << " [popup=" << String::toHexString (reinterpret_cast (this)) << "]"); + + // Don't clear the hover if we're showing a submenu — the mouse likely + // "left" due to a submenu window appearing on top, not because the user + // intentionally moved away. updateSubmenuVisibility keeps submenus open. + if (hasVisibleSubmenu()) + return; + setSelectedItemIndex (-1, true); } @@ -981,18 +1005,29 @@ void PopupMenu::keyDown (const KeyPress& key, const Point& position) void PopupMenu::showSubmenu (int itemIndex) { + YUP_MODULE_DBG (GUI_POPUPMENU, "showSubmenu ENTER: itemIndex=" << itemIndex << " currentSubmenuItemIndex=" << submenuItemIndex << " [popup=" << String::toHexString (reinterpret_cast (this)) << "]"); + if (! canShowSubmenu (itemIndex)) + { + YUP_MODULE_DBG (GUI_POPUPMENU, "showSubmenu EXIT: canShowSubmenu=false [popup=" << String::toHexString (reinterpret_cast (this)) << "]"); return; + } auto& item = *items[itemIndex]; // If we're already showing this submenu, no need to do anything if (isAlreadyShowingSubmenu (itemIndex, item)) + { + YUP_MODULE_DBG (GUI_POPUPMENU, "showSubmenu EXIT: alreadyShowing [popup=" << String::toHexString (reinterpret_cast (this)) << "]"); return; + } // Hide current submenu if different item if (submenuItemIndex != itemIndex) + { + YUP_MODULE_DBG (GUI_POPUPMENU, "showSubmenu: hiding previous submenu (prev=" << submenuItemIndex << ") [popup=" << String::toHexString (reinterpret_cast (this)) << "]"); hideSubmenus(); + } isShowingSubmenu = true; submenuItemIndex = itemIndex; @@ -1005,7 +1040,6 @@ void PopupMenu::showSubmenu (int itemIndex) // Reset the submenu's state before showing to ensure clean positioning currentSubmenu->resetInternalState(); - currentSubmenu->parentMenu = this; // Configure submenu options auto submenuOptions = prepareSubmenuOptions (currentSubmenu); @@ -1016,12 +1050,15 @@ void PopupMenu::showSubmenu (int itemIndex) // Show the submenu with callback currentSubmenu->showCustom (submenuOptions, true, [this] (int selectedID) { + YUP_MODULE_DBG (GUI_POPUPMENU, "showSubmenu callback: selectedID=" << selectedID << " [popup=" << String::toHexString (reinterpret_cast (this)) << "]"); if (selectedID != 0) dismiss (selectedID); isShowingSubmenu = false; }); + YUP_MODULE_DBG (GUI_POPUPMENU, "showSubmenu EXIT: submenu visible=" << (currentSubmenu->isVisible() ? 1 : 0) << " submenu popup=" << String::toHexString (reinterpret_cast (currentSubmenu.get())) << " screenBounds=" << currentSubmenu->getScreenBounds().toString() << " [popup=" << String::toHexString (reinterpret_cast (this)) << "]"); + // Repaint to show active submenu highlight repaint(); } @@ -1040,7 +1077,7 @@ bool PopupMenu::isAlreadyShowingSubmenu (int itemIndex, const Item& item) const return submenuItemIndex == itemIndex && currentSubmenu && currentSubmenu == item.subMenu - && currentSubmenu->isVisible(); + && (currentSubmenu->isVisible() || isShowingSubmenu); } void PopupMenu::positionSubmenu (Options& submenuOptions) @@ -1131,6 +1168,7 @@ void PopupMenu::hideSubmenus() { if (currentSubmenu) { + YUP_MODULE_DBG (GUI_POPUPMENU, "hideSubmenus: dismissing submenu popup=" << String::toHexString (reinterpret_cast (currentSubmenu.get())) << " [popup=" << String::toHexString (reinterpret_cast (this)) << "]"); cleanupSubmenu (currentSubmenu); currentSubmenu = nullptr; @@ -1205,14 +1243,21 @@ void PopupMenu::updateSubmenuVisibility (int hoveredItemIndex) if (item.isSubMenu() && item.isEnabled) { if (submenuItemIndex == hoveredItemIndex && hasVisibleSubmenu()) + { + YUP_MODULE_DBG (GUI_POPUPMENU, "updateSubmenuVisibility: already showing submenu for itemIndex=" << hoveredItemIndex << " [popup=" << String::toHexString (reinterpret_cast (this)) << "]"); return; + } + YUP_MODULE_DBG (GUI_POPUPMENU, "updateSubmenuVisibility: showing submenu for itemIndex=" << hoveredItemIndex << " [popup=" << String::toHexString (reinterpret_cast (this)) << "]"); showSubmenu (hoveredItemIndex); } else { if (hasVisibleSubmenu()) + { + YUP_MODULE_DBG (GUI_POPUPMENU, "updateSubmenuVisibility: hiding submenu (hovered non-submenu itemIndex=" << hoveredItemIndex << ") [popup=" << String::toHexString (reinterpret_cast (this)) << "]"); hideSubmenus(); + } } return; @@ -1225,11 +1270,13 @@ void PopupMenu::updateSubmenuVisibility (int hoveredItemIndex) auto& newItem = *items[hoveredItemIndex]; if (newItem.isSubMenu() && newItem.isEnabled) { + YUP_MODULE_DBG (GUI_POPUPMENU, "updateSubmenuVisibility: switching submenu to itemIndex=" << hoveredItemIndex << " [popup=" << String::toHexString (reinterpret_cast (this)) << "]"); showSubmenu (hoveredItemIndex); return; } else { + YUP_MODULE_DBG (GUI_POPUPMENU, "updateSubmenuVisibility: hiding submenu (hovered non-submenu itemIndex=" << hoveredItemIndex << ") [popup=" << String::toHexString (reinterpret_cast (this)) << "]"); hideSubmenus(); return; } @@ -1269,6 +1316,7 @@ void PopupMenu::setSelectedItemIndex (int index, bool fromMouse) { if (selectedItemIndex == index) { + YUP_MODULE_DBG (GUI_POPUPMENU, "setSelectedItemIndex: unchanged index=" << index << " fromMouse=" << (fromMouse ? 1 : 0) << " [popup=" << String::toHexString (reinterpret_cast (this)) << "]"); if (fromMouse) updateSubmenuVisibility (index); @@ -1283,6 +1331,8 @@ void PopupMenu::setSelectedItemIndex (int index, bool fromMouse) if (selectedItemIndex >= 0 && selectedItemIndex < static_cast (items.size())) items[selectedItemIndex]->isHovered = true; + YUP_MODULE_DBG (GUI_POPUPMENU, "setSelectedItemIndex: index=" << index << " fromMouse=" << (fromMouse ? 1 : 0) << " [popup=" << String::toHexString (reinterpret_cast (this)) << "]"); + if (fromMouse) updateSubmenuVisibility (index); diff --git a/modules/yup_gui/native/yup_Windowing_sdl2.cpp b/modules/yup_gui/native/yup_Windowing_sdl2.cpp index 4df2ab726..66db09f75 100644 --- a/modules/yup_gui/native/yup_Windowing_sdl2.cpp +++ b/modules/yup_gui/native/yup_Windowing_sdl2.cpp @@ -239,7 +239,6 @@ SDL2ComponentNative::~SDL2ComponentNative() // Destroy the window if (window != nullptr) { - SDL_SetWindowData (window, "self", nullptr); SDL_DestroyWindow (window); YUP_MODULE_DBG (GUI_WINDOWING, "SDL2: destroyed window"); window = nullptr; diff --git a/modules/yup_gui/yup_gui.h b/modules/yup_gui/yup_gui.h index 2e313ad28..4377947a8 100644 --- a/modules/yup_gui/yup_gui.h +++ b/modules/yup_gui/yup_gui.h @@ -69,6 +69,16 @@ #define YUP_ENABLE_GUI_WINDOWING_LOGGING 0 #endif +//============================================================================== +/** Config: YUP_ENABLE_GUI_POPUPMENU_LOGGING + + Enable logging of PopupMenu events like hover, submenu show/hide, mouse enter/exit. + Set to 1 to enable, 0 to disable. Off by default. +*/ +#ifndef YUP_ENABLE_GUI_POPUPMENU_LOGGING +#define YUP_ENABLE_GUI_POPUPMENU_LOGGING 0 +#endif + //============================================================================== #include From bbd3a85baf055f03139994e6b9d519aa482df65b Mon Sep 17 00:00:00 2001 From: kunitoki Date: Thu, 2 Jul 2026 12:43:30 +0200 Subject: [PATCH 4/5] More fixes --- examples/audiograph/source/ui/PluginEditorWindow.h | 2 +- modules/yup_gui/native/yup_Windowing_sdl2.cpp | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/examples/audiograph/source/ui/PluginEditorWindow.h b/examples/audiograph/source/ui/PluginEditorWindow.h index daec843f5..d82e3d0d8 100644 --- a/examples/audiograph/source/ui/PluginEditorWindow.h +++ b/examples/audiograph/source/ui/PluginEditorWindow.h @@ -58,7 +58,7 @@ class PluginEditorWindow final : public yup::DocumentWindow { return yup::ComponentNative::Options() .withResizableWindow (editor != nullptr && editor->isResizable()) - .withFramerateRedraw (0) + .withFramerateRedraw (0.0f) .withRenderContinuous (false); } diff --git a/modules/yup_gui/native/yup_Windowing_sdl2.cpp b/modules/yup_gui/native/yup_Windowing_sdl2.cpp index 66db09f75..bc6c99491 100644 --- a/modules/yup_gui/native/yup_Windowing_sdl2.cpp +++ b/modules/yup_gui/native/yup_Windowing_sdl2.cpp @@ -225,6 +225,12 @@ SDL2ComponentNative::~SDL2ComponentNative() updateMouseCapture (false); + // Stop the rendering first, before touching any SDL resources + stopRendering(); + + // Cancel any pending async update that may have been scheduled by the render thread + cancelPendingUpdate(); + // Remove event watch SDL_DelEventWatch (eventDispatcher, this); YUP_MODULE_DBG (GUI_WINDOWING, "SDL2: unregistered window event watch"); @@ -233,12 +239,13 @@ SDL2ComponentNative::~SDL2ComponentNative() Desktop::getInstance()->unregisterNativeComponent (this); YUP_MODULE_DBG (GUI_WINDOWING, "SDL2: unregistered native component"); - // Stop the rendering - stopRendering(); // Destroy the window if (window != nullptr) { + // Clear the window data we set to avoid stale entries in SDL's linked list + SDL_SetWindowData (window, "self", nullptr); + SDL_DestroyWindow (window); YUP_MODULE_DBG (GUI_WINDOWING, "SDL2: destroyed window"); window = nullptr; From 26e61fbbba1f48a020285fa80113a5267c37006b Mon Sep 17 00:00:00 2001 From: kunitoki Date: Thu, 2 Jul 2026 12:53:22 +0200 Subject: [PATCH 5/5] Destroy renderer and context before destroying the window --- modules/yup_gui/native/yup_Windowing_sdl2.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/yup_gui/native/yup_Windowing_sdl2.cpp b/modules/yup_gui/native/yup_Windowing_sdl2.cpp index bc6c99491..cf87f8e77 100644 --- a/modules/yup_gui/native/yup_Windowing_sdl2.cpp +++ b/modules/yup_gui/native/yup_Windowing_sdl2.cpp @@ -239,6 +239,9 @@ SDL2ComponentNative::~SDL2ComponentNative() Desktop::getInstance()->unregisterNativeComponent (this); YUP_MODULE_DBG (GUI_WINDOWING, "SDL2: unregistered native component"); + // Destroy graphics resources before the SDL window + renderer.reset(); + context.reset(); // Destroy the window if (window != nullptr)