From 6c2d42e8711d6703adbac0413a4983edede4c7f3 Mon Sep 17 00:00:00 2001 From: Aleksandra Olemskaia Date: Fri, 10 Jul 2026 12:03:56 +0200 Subject: [PATCH] [settings] IJPL-245458 IJPL-245379 Non-modal settings: fix window disappearing when pinned from macOS full-screen When the Settings window (unpinned/window mode) was in macOS native full-screen and the user pressed Pin, the new FloatDialog was created while macOS was still running the Space-exit animation, making it invisible until a Cmd+Tab round-trip. Fix: switchWindowMode now detects native full-screen via MacFullScreenSupport and defers doSwitchWindowMode until windowExitedFullScreen fires. MacFullScreenSupport gains exitFullScreen(window, onExited) that triggers requestToggleFullScreen and routes the completion callback through the primary FullScreenListener (the only one that JBR reliably delivers events to) (cherry picked from commit dbb0b15c6817d66a07560897457038d02378addc) IJ-CR-217470 GitOrigin-RevId: f5112ce355632d272129f107a68061f08acf6520 --- .../openapi/ui/NonModalWindowWrapper.kt | 36 ++++++++++++++++--- .../intellij/ui/mac/MacFullScreenSupport.java | 24 +++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/ui/NonModalWindowWrapper.kt b/platform/platform-impl/src/com/intellij/openapi/ui/NonModalWindowWrapper.kt index 87004a8849fb..36da9c8f1837 100644 --- a/platform/platform-impl/src/com/intellij/openapi/ui/NonModalWindowWrapper.kt +++ b/platform/platform-impl/src/com/intellij/openapi/ui/NonModalWindowWrapper.kt @@ -33,6 +33,7 @@ import com.intellij.ui.ComponentUtil import com.intellij.ui.FullScreenSupport import com.intellij.ui.ScreenUtil import com.intellij.ui.ToolbarService +import com.intellij.ui.mac.MacFullScreenSupport import com.intellij.util.ui.JBUI import com.intellij.util.ui.UIUtil import com.intellij.util.ui.launchOnShow @@ -119,6 +120,8 @@ abstract class NonModalWindowWrapper( private lateinit var minWindowSize: Dimension private var windowListener: WindowAdapter? = null private var windowDisposable: Disposable? = null + /** Non-null only on macOS while [activeWindow] is a [WindowFrame]. Tracks native full-screen state. */ + private var fullScreenSupport: FullScreenSupport? = null protected var isFloat: Boolean get() = PropertiesComponent.getInstance().getBoolean(floatModeKey, true) @@ -303,10 +306,12 @@ abstract class NonModalWindowWrapper( frame.accessibleContext.accessibleName = getAccessibleWindowName() val wd = Disposer.newDisposable(frameDisposable) windowDisposable = wd + val fss = FullScreenSupport.NEW.apply("com.intellij.ui.mac.MacFullScreenSupport") + fullScreenSupport = fss ToolbarService.getInstance().setTransparentTitleBar( window = frame, rootPane = frame.rootPane, - handlerProvider = { FullScreenSupport.NEW.apply("com.intellij.ui.mac.MacFullScreenSupport") }, + handlerProvider = { fss }, onDispose = { runnable -> Disposer.register(wd) { runnable.run() } }, ) } @@ -381,24 +386,47 @@ abstract class NonModalWindowWrapper( /** * Switches between Float ([JDialog]) and Window ([JFrame]) mode. * The [content] component is reparented and window bounds are transferred. + * + * On macOS, if the [WindowFrame] is in native full-screen when the user pins the window + * (toFloat=true, i.e. switching FROM WindowFrame TO FloatDialog), we first exit full-screen + * via [MacFullScreenSupport.exitFullScreen] and defer the actual switch until the OS animation + * completes. This ensures the new [FloatDialog] is created after macOS has restored the + * pre-full-screen window bounds. On non-macOS, [fullScreenSupport] is always null. */ private fun switchWindowMode(toFloat: Boolean) { - val bounds = activeWindow.bounds + val macFss = if (toFloat) fullScreenSupport as? MacFullScreenSupport else null + if (macFss != null && macFss.isFullScreen) { + val ideFrame = getIdeJFrame() + macFss.exitFullScreen(activeWindow) { + if (!isDisposed) doSwitchWindowMode(toFloat, ideFrame) + } + return + } + doSwitchWindowMode(toFloat) + } + + private fun doSwitchWindowMode(toFloat: Boolean, ideFrameToActivate: JFrame? = null) { + val oldBounds = activeWindow.bounds val wasVisible = activeWindow.isVisible val savedDefaultButton = (activeWindow as RootPaneContainer).rootPane.defaultButton windowListener?.let { activeWindow.removeWindowListener(it) } windowListener = null content.parent?.remove(content) + fullScreenSupport = null disposeWindow(activeWindow) windowDisposable?.let { Disposer.dispose(it) } windowDisposable = null - activeWindow = createAwtWindow(toFloat, content, minWindowSize, bounds.size) + + activeWindow = createAwtWindow(toFloat, content, minWindowSize, oldBounds.size) fitWindowToScreen(activeWindow) installWindowListeners() savedDefaultButton?.let { (activeWindow as RootPaneContainer).rootPane.defaultButton = it } - activeWindow.bounds = bounds + activeWindow.bounds = oldBounds dimensionKey?.let { WindowStateService.getInstance(project).getState(it, activeWindow) } if (wasVisible) { + // When switching back from full-screen, first bring the IDE frame to front so macOS + // navigates to the IDE Space, then show the dialog on top of it. + ideFrameToActivate?.toFront() activeWindow.isVisible = true activeWindow.toFront() activeWindow.requestFocus() diff --git a/platform/platform-impl/src/com/intellij/ui/mac/MacFullScreenSupport.java b/platform/platform-impl/src/com/intellij/ui/mac/MacFullScreenSupport.java index 405cbc78b30c..58703a0a36b8 100644 --- a/platform/platform-impl/src/com/intellij/ui/mac/MacFullScreenSupport.java +++ b/platform/platform-impl/src/com/intellij/ui/mac/MacFullScreenSupport.java @@ -1,14 +1,17 @@ // Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.ui.mac; +import com.apple.eawt.Application; import com.apple.eawt.FullScreenListener; import com.apple.eawt.FullScreenUtilities; import com.apple.eawt.event.FullScreenEvent; import com.intellij.ui.FullScreenSupport; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.RootPaneContainer; +import javax.swing.SwingUtilities; import java.awt.Window; /** @@ -22,6 +25,7 @@ import java.awt.Window; public final class MacFullScreenSupport implements FullScreenSupport { private FullScreenListener myListener; private boolean myIsFullScreen; + private @Nullable Runnable myOnExitedCallback; @Override public boolean isFullScreen() { @@ -49,11 +53,31 @@ public final class MacFullScreenSupport implements FullScreenSupport { @Override public void windowExitedFullScreen(FullScreenEvent event) { myIsFullScreen = false; + Runnable callback = myOnExitedCallback; + myOnExitedCallback = null; + if (callback != null) { + SwingUtilities.invokeLater(callback); + } } }; FullScreenUtilities.addFullScreenListenerTo(window, myListener); } + /** + * Programmatically exits macOS native full-screen and invokes {@code onExited} once the OS + * animation completes (after {@code windowExitedFullScreen}). If the window is not in + * full-screen, {@code onExited} is called immediately on the calling thread. + * Not part of {@link FullScreenSupport} — this is a macOS-specific detail. + */ + public void exitFullScreen(@NotNull Window window, @NotNull Runnable onExited) { + if (!myIsFullScreen) { + onExited.run(); + return; + } + myOnExitedCallback = onExited; + Application.getApplication().requestToggleFullScreen(window); + } + @Override public void removeListener(@NotNull Window window) { if (window instanceof RootPaneContainer container && container.getRootPane() != null) {