From 0d67de8403ebc0dffc1a033db29d18dd2c1bf893 Mon Sep 17 00:00:00 2001 From: Alexey Merkulov Date: Fri, 1 Dec 2023 16:37:05 +0100 Subject: [PATCH] IDEA-206346 Implement a hack to reduce variables tree flickering: freeze repainting for some time after update started It is under registry debugger.anti.flickering.delay. The solution is applied only if that key is not set to zero. Currently, this solution is breaking autoscroll to the selected element in the variable tree. GitOrigin-RevId: fb5d505abfef53c977f50779a14d098a605ddd33 --- .../intellij/ide/ui/AntiFlickeringPanel.kt | 70 +++++++++++++++++++ .../util/resources/misc/registry.properties | 3 + .../impl/frame/XVariablesViewBase.java | 8 +++ .../impl/ui/tree/XDebuggerTreePanel.java | 13 +++- 4 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 platform/platform-impl/src/com/intellij/ide/ui/AntiFlickeringPanel.kt diff --git a/platform/platform-impl/src/com/intellij/ide/ui/AntiFlickeringPanel.kt b/platform/platform-impl/src/com/intellij/ide/ui/AntiFlickeringPanel.kt new file mode 100644 index 000000000000..05209481cc72 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ide/ui/AntiFlickeringPanel.kt @@ -0,0 +1,70 @@ +// 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.ide.ui + +import com.intellij.ide.ui.UISettings.Companion.setupAntialiasing +import com.intellij.ui.DirtyUI +import com.intellij.util.SingleAlarm +import com.intellij.util.ui.ImageUtil +import com.intellij.util.ui.UIUtil +import org.jetbrains.annotations.ApiStatus +import java.awt.Component +import java.awt.Dimension +import java.awt.Graphics +import java.awt.LayoutManager +import java.awt.image.BufferedImage +import javax.swing.JPanel + +/** A hacky way to reduce flickering. */ +@ApiStatus.Internal +class AntiFlickeringPanel(layout: LayoutManager?) : JPanel(layout) { + private var savedSelfieImage: BufferedImage? = null + private var savedSize: Dimension? = null + private var savedPreferredSize: Dimension? = null + + fun freezePainting(delay: Int) { + isOpaque = true + savedSelfieImage = takeSelfie(this) + savedSize = size + savedPreferredSize = preferredSize + + val alarm = SingleAlarm({ + savedSelfieImage = null + savedSize = null + savedPreferredSize = null + isOpaque = false + revalidate() + repaint() + }, delay, null) + alarm.request() + } + + override fun getSize(): Dimension { + return savedSize ?: super.getSize() + } + + override fun getPreferredSize(): Dimension { + return savedPreferredSize ?: super.getPreferredSize() + } + + @DirtyUI + override fun paint(g: Graphics) { + val image = savedSelfieImage + if (image != null) { + System.err.println("image") + UIUtil.drawImage(g, image, 0, 0, null) + return + } + super.paint(g) + } + + companion object { + @JvmStatic + private fun takeSelfie(component: Component): BufferedImage { + val graphicsConfiguration = component.graphicsConfiguration + val image = ImageUtil.createImage(graphicsConfiguration, component.width, component.height, BufferedImage.TYPE_INT_ARGB) + setupAntialiasing(image.graphics) + component.paint(image.graphics) + return image + } + } +} diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index 25fba149d38c..df34128fd7c6 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -626,6 +626,9 @@ debugger.show.breakpoints.inline.even.trivial=false debugger.show.breakpoints.inline.even.trivial.description=Show inline inlay for line breakpoint even if it's the only breakpoint debugger.inlayRunToCursor.hover.area=4 debugger.inlayRunToCursor.hover.area.description=The size of hover area in the editor to show inlay Run-To-Cursor popup (counting in characters) +debugger.anti.flickering.delay=0 +debugger.anti.flickering.delay.description=Use anti-flickering panel atop of debugger variable tree and freeze repainting on specified ms \ + after stepping. Zero value means do not use anti-flickering panel. Need to restart debug after setting it to zero and back. execution.java.always.debug=false execution.java.always.debug.description=Always run java processes with the debug agent diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/frame/XVariablesViewBase.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/frame/XVariablesViewBase.java index ad546839d39b..ae1251c384ca 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/frame/XVariablesViewBase.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/frame/XVariablesViewBase.java @@ -2,6 +2,7 @@ package com.intellij.xdebugger.impl.frame; import com.intellij.ide.dnd.DnDManager; +import com.intellij.ide.ui.AntiFlickeringPanel; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.LogicalPosition; import com.intellij.openapi.editor.event.SelectionEvent; @@ -79,6 +80,13 @@ public abstract class XVariablesViewBase extends XDebugView { protected void buildTreeAndRestoreState(@NotNull final XStackFrame stackFrame) { XSourcePosition position = stackFrame.getSourcePosition(); XDebuggerTree tree = getTree(); + Container treeParent = tree.getParent(); + if (treeParent instanceof AntiFlickeringPanel antiFlickeringPanel) { + int delay = Registry.intValue("debugger.anti.flickering.delay", 0); + if (delay > 0) { + antiFlickeringPanel.freezePainting(delay); + } + } tree.setSourcePosition(position); createNewRootNode(stackFrame); XVariablesView.InlineVariablesInfo.set(getSession(tree), new XVariablesView.InlineVariablesInfo()); diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/XDebuggerTreePanel.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/XDebuggerTreePanel.java index bf2667f85fbd..7969d8d9327c 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/XDebuggerTreePanel.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/XDebuggerTreePanel.java @@ -5,10 +5,12 @@ import com.intellij.ide.dnd.DnDAction; import com.intellij.ide.dnd.DnDDragStartBean; import com.intellij.ide.dnd.DnDSource; import com.intellij.ide.dnd.aware.DnDAwareTree; +import com.intellij.ide.ui.AntiFlickeringPanel; import com.intellij.openapi.Disposable; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.registry.Registry; import com.intellij.ui.ScrollPaneFactory; import com.intellij.xdebugger.XDebuggerBundle; import com.intellij.xdebugger.XSourcePosition; @@ -31,7 +33,16 @@ public final class XDebuggerTreePanel implements DnDSource { @NotNull @NonNls final String popupActionGroupId, @Nullable XValueMarkers markers) { myTree = new XDebuggerTree(project, editorsProvider, sourcePosition, popupActionGroupId, markers); myMainPanel = new JPanel(new BorderLayout()); - myMainPanel.add(ScrollPaneFactory.createScrollPane(myTree), BorderLayout.CENTER); + Component content; + if (Registry.intValue("debugger.anti.flickering.delay", 0) > 0) { + AntiFlickeringPanel antiFlickeringPanel = new AntiFlickeringPanel(new BorderLayout()); + antiFlickeringPanel.add(myTree); + content = antiFlickeringPanel; + } + else { + content = myTree; + } + myMainPanel.add(ScrollPaneFactory.createScrollPane(content), BorderLayout.CENTER); Disposer.register(parentDisposable, myTree); Disposer.register(parentDisposable, new Disposable() { @Override