Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Popups should use native windows #97

Open
rock3r opened this issue Aug 28, 2023 · 9 comments
Open

Popups should use native windows #97

rock3r opened this issue Aug 28, 2023 · 9 comments
Labels
blocked-externally This issue or feature is blocked by external dependencies (e.g., bugs in Compose) consistency Our UI presentation is not consistent with IJ

Comments

@rock3r
Copy link
Collaborator

rock3r commented Aug 28, 2023

Our popup windows (e.g., menus, dropdowns, tooltips, etc) don't use native Swing popup windows. This causes a number of issues, such as:

  1. Popups are limited to showing inside the canvas that generated them
  2. They may not match the other Swing native windows in behavior
  3. Shadow needs to be implemented per-OS and it's hard to match OS behaviour, especially on Linux where there are a million variables at play
@rock3r rock3r added the consistency Our UI presentation is not consistent with IJ label Aug 28, 2023
@rock3r rock3r added this to the Build 1.0.0 milestone Aug 28, 2023
@rock3r rock3r assigned Walingar and unassigned fscarponi Oct 19, 2023
@rock3r
Copy link
Collaborator Author

rock3r commented Nov 30, 2023

This depends on JetBrains/compose-multiplatform#2924 being done

@rock3r rock3r added the blocked-externally This issue or feature is blocked by external dependencies (e.g., bugs in Compose) label Nov 30, 2023
@rock3r
Copy link
Collaborator Author

rock3r commented Nov 30, 2023

@Walingar we have internal users who are strongly requesting this. Can we have a commitment on a delivery date ASAP?

@Walingar
Copy link
Collaborator

Walingar commented Feb 2, 2024

As I know 1.6.0 Compose Multiplatform will provide some ways to show popup outside of the window.

Also there is a way to use JBPopup (that I will recommend), see this code as an example: https://github.com/JetBrains/intellij-community/blob/241.9959/platform/compose/src/com/intellij/platform/compose/ActionGroupPopup.kt

@Walingar Walingar removed their assignment Feb 2, 2024
@devkanro
Copy link
Collaborator

devkanro commented Feb 3, 2024

Also there is a way to use JBPopup (that I will recommend)

But we can use JBPopup only in bridge, standalone should support it too

@rock3r
Copy link
Collaborator Author

rock3r commented Feb 5, 2024

Given we're on Compose 1.6 already, I reckon there should be an API to use for that in standalone apps? The way I see it, the API should be common between bridge and standalone, but the actual implementation should use the best available API.

@rock3r
Copy link
Collaborator Author

rock3r commented Feb 27, 2024

JetBrains/compose-multiplatform-core#992 contains a new API for popups in 1.6.0 that we can use. We need to turn on a flag to enable the new behaviour on tooltips and popup APIs. Thanks @MatkovIvan for letting me know!

@YasserDbeis
Copy link
Collaborator

YasserDbeis commented Apr 15, 2024

This bug is causing an issue with compose tooltips at the border of compose and swing components getting cut off so I tried adding the following line as indicated in JetBrains/compose-multiplatform-core#992: System.setProperty("compose.layers.type", "COMPONENT")

I then tested an IconButton wrapped in a tooltip, where the tooltip content was a simple Text("foo"). The component was not consistently hoverable anymore and had no tooltip at all. It also took a few clicks before the onClick handler of the IconButton was invoked. Some of these symptoms can be attributed to the exception thrown on hover:


java.lang.IllegalStateException: No TextStyle provided. Have you forgotten the theme?
	at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt:46)
	at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2.invokeSuspend(Recomposer.kt:537)
	at androidx.compose.runtime.Recomposer$recompositionRunner$2$3.invokeSuspend(Recomposer.kt:946)
	at androidx.compose.runtime.Recomposer$recompositionRunner$2.invokeSuspend(Recomposer.kt:945)
	at androidx.compose.ui.scene.ComposeSceneRecomposer$1.invokeSuspend(ComposeSceneRecomposer.skiko.kt:72)
	Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [androidx.compose.ui.scene.ComposeContainer$DesktopCoroutineExceptionHandler@3311a37e, androidx.compose.runtime.BroadcastFrameClock@3980788f, CoroutineId(7078), "coroutine#7078":StandaloneCoroutine{Cancelled}@40e545f8, FlushCoroutineDispatcher@12c35d03]
Caused by: java.lang.IllegalStateException: No TextStyle provided. Have you forgotten the theme?
	at org.jetbrains.jewel.foundation.theme.JewelThemeKt$LocalTextStyle$1.invoke(JewelTheme.kt:101)
	at org.jetbrains.jewel.foundation.theme.JewelThemeKt$LocalTextStyle$1.invoke(JewelTheme.kt:100)
	at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
	at androidx.compose.runtime.LazyValueHolder.getCurrent(ValueHolders.kt:29)
	at androidx.compose.runtime.LazyValueHolder.getValue(ValueHolders.kt:31)
	at androidx.compose.runtime.CompositionLocalMapKt.read(CompositionLocalMap.kt:88)
	at androidx.compose.runtime.ComposerImpl.consume(Composer.kt:2050)
	at org.jetbrains.jewel.foundation.theme.JewelTheme$Companion.getTextStyle(JewelTheme.kt:114)
	at org.jetbrains.jewel.ui.component.TextKt.Text-fLXpl1I(Text.kt:39)
	at com.android.tools.profilers.taskbased.tabs.taskgridandbars.taskbars.ComposableSingletons$TaskTitleBarKt$lambda-1$1.invoke(TaskTitleBar.kt:66)
	at com.android.tools.profilers.taskbased.tabs.taskgridandbars.taskbars.ComposableSingletons$TaskTitleBarKt$lambda-1$1.invoke(TaskTitleBar.kt:66)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:108)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
	at org.jetbrains.jewel.ui.component.TooltipKt$Tooltip$1$1$1$1.invoke(Tooltip.kt:72)
	at org.jetbrains.jewel.ui.component.TooltipKt$Tooltip$1$1$1$1.invoke(Tooltip.kt:71)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:108)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
	at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
	at org.jetbrains.jewel.foundation.theme.JewelThemeKt.OverrideDarkMode(JewelTheme.kt:109)
	at org.jetbrains.jewel.ui.component.TooltipKt$Tooltip$1$1.invoke(Tooltip.kt:71)
	at org.jetbrains.jewel.ui.component.TooltipKt$Tooltip$1$1.invoke(Tooltip.kt:51)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:108)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
	at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
	at org.jetbrains.jewel.ui.component.TooltipKt$Tooltip$1.invoke(Tooltip.kt:49)
	at org.jetbrains.jewel.ui.component.TooltipKt$Tooltip$1.invoke(Tooltip.kt:48)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:108)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
	at androidx.compose.foundation.TooltipArea_desktopKt$TooltipArea$6$2.invoke(TooltipArea.desktop.kt:170)
	at androidx.compose.foundation.TooltipArea_desktopKt$TooltipArea$6$2.invoke(TooltipArea.desktop.kt:159)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:108)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
	at androidx.compose.ui.window.Popup_skikoKt$PopupLayout$2$1.invoke(Popup.skiko.kt:559)
	at androidx.compose.ui.window.Popup_skikoKt$PopupLayout$2$1.invoke(Popup.skiko.kt:452)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:108)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
	at androidx.compose.ui.platform.ZeroInsetsConfig.excludeSafeInsets(PlatformInsets.skiko.kt:95)
	at androidx.compose.ui.window.Popup_skikoKt$PopupLayout$2.invoke(Popup.skiko.kt:452)
	at androidx.compose.ui.window.Popup_skikoKt$PopupLayout$2.invoke(Popup.skiko.kt:439)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:108)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
	at androidx.compose.runtime.RecomposeScopeImpl.compose(RecomposeScopeImpl.kt:169)
	at androidx.compose.runtime.ComposerImpl.recomposeToGroupEnd(Composer.kt:2469)
	at androidx.compose.runtime.ComposerImpl.skipCurrentGroup(Composer.kt:2738)
	at androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:3353)
	at androidx.compose.runtime.ComposerImpl.recompose$runtime(Composer.kt:3304)
	at androidx.compose.runtime.CompositionImpl.recompose(Composition.kt:781)
	at androidx.compose.runtime.Recomposer.performRecompose(Recomposer.kt:1097)
	at androidx.compose.runtime.Recomposer.access$performRecompose(Recomposer.kt:124)
	at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$1.invoke(Recomposer.kt:569)
	at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$1.invoke(Recomposer.kt:537)
	at androidx.compose.runtime.BroadcastFrameClock$FrameAwaiter.resume(BroadcastFrameClock.kt:42)
	at androidx.compose.runtime.BroadcastFrameClock.sendFrame(BroadcastFrameClock.kt:71)
	at androidx.compose.ui.scene.BaseComposeScene.render(BaseComposeScene.skiko.kt:150)
	at androidx.compose.ui.scene.ComposeSceneMediator$DesktopSkikoView.onRender(ComposeSceneMediator.desktop.kt:497)
	at org.jetbrains.skiko.swing.SkiaSwingLayer$skikoViewWithClipping$1.onRender(SkiaSwingLayer.kt:50)
	at org.jetbrains.skiko.swing.MetalSwingRedrawer$onRender$1$1.invoke(MetalSwingRedrawer.kt:73)
	at org.jetbrains.skiko.swing.MetalSwingRedrawer$onRender$1$1.invoke(MetalSwingRedrawer.kt:59)
	at org.jetbrains.skiko.ResourceUtilsKt.autoCloseScope(ResourceUtils.kt:34)
	at org.jetbrains.skiko.swing.MetalSwingRedrawer.onRender(MetalSwingRedrawer.kt:59)
	at org.jetbrains.skiko.swing.SwingRedrawerBase.redraw(SwingRedrawerBase.kt:51)
	at org.jetbrains.skiko.swing.SkiaSwingLayer.paint(SkiaSwingLayer.kt:111)
	at androidx.compose.ui.scene.skia.SwingSkiaLayerComponent$contentComponent$1.paint(SwingSkiaLayerComponent.desktop.kt:50)
	at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:955)
	at java.desktop/javax.swing.JComponent.paint(JComponent.java:1124)
	at java.desktop/javax.swing.JLayeredPane.paint(JLayeredPane.java:586)
	at androidx.compose.ui.scene.SwingComposeSceneLayer$container$1.paint(SwingComposeSceneLayer.desktop.kt:63)
	at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:955)
	at java.desktop/javax.swing.JComponent.paint(JComponent.java:1124)
	at java.desktop/javax.swing.JLayeredPane.paint(JLayeredPane.java:586)
	at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:955)
	at java.desktop/javax.swing.JComponent.paint(JComponent.java:1124)
	at java.desktop/javax.swing.JComponent.paintToOffscreen(JComponent.java:5312)
	at java.desktop/javax.swing.BufferStrategyPaintManager.paint(BufferStrategyPaintManager.java:247)
	at java.desktop/javax.swing.RepaintManager.paint(RepaintManager.java:1347)
	at java.desktop/javax.swing.JComponent._paintImmediately(JComponent.java:5260)
	at java.desktop/javax.swing.JComponent.paintImmediately(JComponent.java:5070)
	at java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:882)
	at java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:865)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
	at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:865)
	at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:838)
	at java.desktop/javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:787)
	at java.desktop/javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1909)
	at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:318)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:792)
	at java.desktop/java.awt.EventQueue$3.run(EventQueue.java:739)
	at java.desktop/java.awt.EventQueue$3.run(EventQueue.java:733)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:761)
	at com.intellij.ide.IdeEventQueue.defaultDispatchEvent(IdeEventQueue.kt:698)
	at com.intellij.ide.IdeEventQueue._dispatchEvent$lambda$12(IdeEventQueue.kt:593)
	at com.intellij.openapi.application.impl.RwLockHolder.runWithoutImplicitRead(RwLockHolder.kt:105)
	at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.kt:593)
	at com.intellij.ide.IdeEventQueue.access$_dispatchEvent(IdeEventQueue.kt:77)
	at com.intellij.ide.IdeEventQueue$dispatchEvent$processEventRunnable$1$1$1.compute(IdeEventQueue.kt:362)
	at com.intellij.ide.IdeEventQueue$dispatchEvent$processEventRunnable$1$1$1.compute(IdeEventQueue.kt:361)
	at com.intellij.openapi.progress.impl.CoreProgressManager.computePrioritized(CoreProgressManager.java:843)
	at com.intellij.ide.IdeEventQueue$dispatchEvent$processEventRunnable$1$1.invoke(IdeEventQueue.kt:361)
	at com.intellij.ide.IdeEventQueue$dispatchEvent$processEventRunnable$1$1.invoke(IdeEventQueue.kt:356)
	at com.intellij.ide.IdeEventQueueKt.performActivity$lambda$1(IdeEventQueue.kt:1021)
	at com.intellij.openapi.application.TransactionGuardImpl.performActivity(TransactionGuardImpl.java:106)
	at com.intellij.ide.IdeEventQueueKt.performActivity(IdeEventQueue.kt:1021)
	at com.intellij.ide.IdeEventQueue.dispatchEvent$lambda$7(IdeEventQueue.kt:356)
	at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.kt:393)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:207)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:128)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:117)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:113)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:105)
	at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:92)

However, after adding style = TextStyles.Default to circumvent the exception, all of the behavior was the same but it did not throw the exception. Just adding this as some context/a data point.

With that said, it would be amazing if we could prioritize this change's integration into Jewel to unblock the usage of popup elements.

@YasserDbeis
Copy link
Collaborator

YasserDbeis commented Apr 15, 2024

I went ahead and sent in my own custom tooltipPlacement to adjust the alignment depending on where the component on the screen is and it seemed to do the trick (avoids crossing the compose-swing boundary). So I suppose this can serve as somewhat of a workaround in the meantime; it does not completely resolve the issue if the application window's width is small enough.

@rock3r
Copy link
Collaborator Author

rock3r commented Apr 16, 2024

CC @obask if you end up picking this up after you're done with the current markdown stuff...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
blocked-externally This issue or feature is blocked by external dependencies (e.g., bugs in Compose) consistency Our UI presentation is not consistent with IJ
Projects
None yet
Development

No branches or pull requests

5 participants