IDEA-222026 implement heavyweight facade heuristic for jcef overlapping problem

GitOrigin-RevId: 2094959a1c28859390df7c7e9b7a0923968471a8
This commit is contained in:
Anton Tarasov
2019-12-02 14:01:43 +00:00
committed by intellij-monorepo-bot
parent c26118f61f
commit 2c0c7ec040
5 changed files with 327 additions and 2 deletions
@@ -106,4 +106,19 @@ public class JdkEx {
return null;
}
}
public static void setIgnoreMouseEvents(@NotNull Window window, boolean ignoreMouseEvents) {
if (SystemInfo.isJetBrainsJvm && SystemInfo.isMac) {
window.setEnabled(false);
try {
MethodInvocator invocator =
new MethodInvocator(false, Class.forName("java.awt.Window"), "setIgnoreMouseEvents", boolean.class);
if (invocator.isAvailable()) {
invocator.invoke(window, ignoreMouseEvents);
}
}
catch (ClassNotFoundException ignore) {
}
}
}
}
@@ -30,6 +30,8 @@ import com.intellij.openapi.wm.WeakFocusStackManager;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.ui.components.panels.NonOpaquePanel;
import com.intellij.ui.components.panels.Wrapper;
import com.intellij.ui.jcef.HwFacadeJPanel;
import com.intellij.ui.jcef.HwFacadeNonOpaquePanel;
import com.intellij.ui.scale.JBUIScale;
import com.intellij.util.Alarm;
import com.intellij.util.Consumer;
@@ -1634,7 +1636,7 @@ public final class BalloonImpl implements Balloon, IdeTooltip.Ui, ScreenAreaCons
void layout(@NotNull Rectangle bounds);
}
public class ActionButton extends NonOpaquePanel implements IdeGlassPane.TopComponent {
public class ActionButton extends HwFacadeNonOpaquePanel implements IdeGlassPane.TopComponent {
private final Icon myIcon;
private final Icon myHoverIcon;
private final Consumer<? super MouseEvent> myListener;
@@ -1697,7 +1699,7 @@ public final class BalloonImpl implements Balloon, IdeTooltip.Ui, ScreenAreaCons
}
}
private class MyComponent extends JPanel implements ComponentWithMnemonics {
private class MyComponent extends HwFacadeJPanel implements ComponentWithMnemonics {
private BufferedImage myImage;
private float myAlpha;
@@ -0,0 +1,215 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ui.jcef;
import com.intellij.jdkEx.JdkEx;
import com.intellij.util.FieldAccessor;
import org.cef.CefApp;
import org.cef.CefClient;
import org.cef.browser.CefBrowser;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import sun.awt.AWTAccessor;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.image.VolatileImage;
import java.util.*;
import java.util.List;
import java.util.function.Consumer;
/**
* Provides a heavyweight window "facade" for a lightweight component.
* Used to workaround the problem of overlapping a heavyweight JCEF browser component.
* The "facade" means that a hw window follows the lifecycle of the target lw component
* (add/show/hide/dispose), follows its bounds and intercepts its painting.
* The hw window itself is non-focusable and mouse-transparent, so the target lw
* component handles mouse events as usual.
*
* @author tav
*/
public class HwFacadeHelper {
@SuppressWarnings("UseJBColor")
public static final Color TRANSPARENT_COLOR = new Color(1, 1, 1, 0);
private final JComponent myTarget;
private Window myHwFacade;
private ComponentAdapter myOwnerListener;
private ComponentAdapter myTargetListener;
private VolatileImage myBackBuffer;
// [tav] todo: export visible browser bounds from jcef instead
private static class JCEFAccessor {
private static FieldAccessor<CefApp, HashSet<CefClient>> clientsField;
private static FieldAccessor<CefClient, HashMap<Integer, CefBrowser>> browsersField;
private static CefApp ourCefApp;
@Nullable
public static CefApp getCefApp() {
if (ourCefApp == null && CefApp.getState() != CefApp.CefAppState.NONE) {
ourCefApp = CefApp.getInstance();
clientsField = new FieldAccessor<>(CefApp.class, "clients_");
browsersField = new FieldAccessor<>(CefClient.class, "browser_");
}
return ourCefApp;
}
@NotNull
public static List<CefBrowser> getBrowsers() {
List<CefBrowser> list = new LinkedList<>();
if (getCefApp() != null && clientsField.isAvailable() && browsersField.isAvailable()) {
Set<CefClient> clients = clientsField.get(ourCefApp);
if (clients != null) {
for (CefClient client : clients) {
HashMap<?, CefBrowser> browsers = browsersField.get(client);
if (browsers != null) {
list.addAll(browsers.values());
}
}
}
}
return list;
}
}
public HwFacadeHelper(JComponent target) {
myTarget = target;
}
private boolean isActive() {
return myHwFacade != null;
}
private static boolean isCefAppActive() {
return JCEFAccessor.getCefApp() != null;
}
private void onShowing() {
if (!isCefAppActive()) return;
assert myHwFacade == null;
assert myTarget.isVisible();
myTarget.addComponentListener(myTargetListener = new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
if (isActive()) {
myHwFacade.setSize(myTarget.getSize());
}
else {
activateIfNeeded();
}
}
@Override
public void componentMoved(ComponentEvent e) {
if (isActive()) {
if (myHwFacade.isVisible()) myHwFacade.setLocation(myTarget.getLocationOnScreen());
}
else {
activateIfNeeded();
}
}
});
activateIfNeeded();
}
private void activateIfNeeded() {
if (!isCefAppActive()) return;
Rectangle targetBounds = new Rectangle(myTarget.getLocationOnScreen(), myTarget.getSize());
boolean overlaps = false;
for (CefBrowser browser : JCEFAccessor.getBrowsers()) {
Component comp = browser.getUIComponent();
if (comp != null && comp.isVisible() && comp.isShowing() &&
new Rectangle(comp.getLocationOnScreen(), comp.getSize()).intersects(targetBounds))
{
overlaps = true;
break;
}
}
if (overlaps) {
Window owner = SwingUtilities.getWindowAncestor(myTarget);
owner.addComponentListener(myOwnerListener = new ComponentAdapter() {
@Override
public void componentMoved(ComponentEvent e) {
if (myTarget.isVisible()) {
myHwFacade.setLocation(myTarget.getLocationOnScreen());
}
}
});
myHwFacade = new Window(owner) {
@Override
public void paint(Graphics g) {
super.paint(g);
if (myBackBuffer != null) {
g.drawImage(myBackBuffer, 0, 0, null);
}
}
};
JdkEx.setIgnoreMouseEvents(myHwFacade, true);
myHwFacade.setBounds(targetBounds);
myHwFacade.setFocusableWindowState(false);
myHwFacade.setBackground(TRANSPARENT_COLOR);
myHwFacade.setVisible(true);
}
}
public void addNotify() {
if (myTarget.isVisible()) {
onShowing();
}
}
public void show() {
if (!isCefAppActive()) return;
if (AWTAccessor.getComponentAccessor().getPeer(myTarget) != null) {
if (isActive()) {
myHwFacade.setVisible(true);
}
else {
onShowing();
}
}
}
public void removeNotify() {
if (isActive()) {
myHwFacade.dispose();
myHwFacade = null;
myBackBuffer = null;
myTarget.removeComponentListener(myTargetListener);
Window owner = SwingUtilities.getWindowAncestor(myTarget);
if (owner != null) owner.removeComponentListener(myOwnerListener);
}
}
public void hide() {
if (isActive()) {
myHwFacade.setVisible(false);
}
}
public void paint(Graphics g, Consumer<Graphics> targetPaint) {
if (isActive()) {
Dimension size = myTarget.getSize();
if (myBackBuffer == null || myBackBuffer.getWidth() != size.width || myBackBuffer.getHeight() != size.height) {
myBackBuffer = GraphicsEnvironment.
getLocalGraphicsEnvironment().
getDefaultScreenDevice().
getDefaultConfiguration().
createCompatibleVolatileImage(size.width, size.height, Transparency.TRANSLUCENT);
}
Graphics2D bbGraphics = (Graphics2D)myBackBuffer.getGraphics();
bbGraphics.setBackground(TRANSPARENT_COLOR);
bbGraphics.clearRect(0, 0, size.width, size.height);
targetPaint.accept(bbGraphics);
myHwFacade.repaint();
}
else {
targetPaint.accept(g);
}
}
}
@@ -0,0 +1,46 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ui.jcef;
import javax.swing.*;
import java.awt.*;
/**
* A heavyweight facade for {@link JPanel}.
*
* @see HwFacadeHelper
* @author tav
*/
public class HwFacadeJPanel extends JPanel {
private final HwFacadeHelper myHwFacadeHelper = new HwFacadeHelper(this);
@Override
public void addNotify() {
super.addNotify();
myHwFacadeHelper.addNotify();
}
@Override
public void removeNotify() {
super.removeNotify();
myHwFacadeHelper.removeNotify();
}
@SuppressWarnings("deprecation")
@Override
public void show() {
super.show();
myHwFacadeHelper.show();
}
@SuppressWarnings("deprecation")
@Override
public void hide() {
super.hide();
myHwFacadeHelper.hide();
}
@Override
public void paint(Graphics g) {
myHwFacadeHelper.paint(g, (gg) -> super.paint(gg));
}
}
@@ -0,0 +1,47 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ui.jcef;
import com.intellij.ui.components.panels.NonOpaquePanel;
import java.awt.*;
/**
* A heavyweight facade for {@link NonOpaquePanel}.
*
* @see HwFacadeHelper
* @author tav
*/
public class HwFacadeNonOpaquePanel extends NonOpaquePanel {
private final HwFacadeHelper myHwFacadeHelper = new HwFacadeHelper(this);
@Override
public void addNotify() {
super.addNotify();
myHwFacadeHelper.addNotify();
}
@Override
public void removeNotify() {
super.removeNotify();
myHwFacadeHelper.removeNotify();
}
@SuppressWarnings("deprecation")
@Override
public void show() {
super.show();
myHwFacadeHelper.show();
}
@SuppressWarnings("deprecation")
@Override
public void hide() {
super.hide();
myHwFacadeHelper.hide();
}
@Override
public void paint(Graphics g) {
myHwFacadeHelper.paint(g, (gg) -> super.paint(gg));
}
}