mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-222026 implement heavyweight facade heuristic for jcef overlapping problem
GitOrigin-RevId: 2094959a1c28859390df7c7e9b7a0923968471a8
This commit is contained in:
committed by
intellij-monorepo-bot
parent
c26118f61f
commit
2c0c7ec040
@@ -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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user