mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-16 14:23:28 +07:00
- bring back JBCefOSRHandlerFactory#createScreenBoundsProvider - bring back the default implementation of JBCefOSRHandlerFactory (cherry picked from commit 3806ead7222d61503572edb3fadafcc12d624b59) IJ-CR-148101 GitOrigin-RevId: 9d2750d88b61bf9f14ae3ce983213a4e45756f18
317 lines
11 KiB
Java
317 lines
11 KiB
Java
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
|
package com.intellij.ui.jcef;
|
|
|
|
import com.intellij.openapi.util.registry.RegistryManager;
|
|
import com.intellij.ui.Gray;
|
|
import com.intellij.util.Function;
|
|
import com.intellij.util.JBHiDPIScaledImage;
|
|
import com.intellij.util.RetinaImage;
|
|
import com.intellij.util.ui.UIUtil;
|
|
import org.cef.OS;
|
|
import org.cef.browser.CefBrowser;
|
|
import org.cef.callback.CefDragData;
|
|
import org.cef.handler.CefRenderHandler;
|
|
import org.cef.handler.CefScreenInfo;
|
|
import org.cef.misc.CefRange;
|
|
import org.jetbrains.annotations.NotNull;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
import javax.swing.*;
|
|
import java.awt.*;
|
|
import java.awt.image.BufferedImage;
|
|
import java.awt.image.DataBufferInt;
|
|
import java.awt.image.VolatileImage;
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.ByteOrder;
|
|
import java.nio.IntBuffer;
|
|
import java.util.concurrent.atomic.AtomicReference;
|
|
|
|
/**
|
|
* A render handler for an off-screen browser.
|
|
*
|
|
* @author tav
|
|
* @see JBCefOsrComponent
|
|
*/
|
|
class JBCefOsrHandler implements CefRenderHandler {
|
|
private final @NotNull JBCefFpsMeter myFpsMeter = JBCefFpsMeter.register(
|
|
RegistryManager.getInstance().get("ide.browser.jcef.osr.measureFPS.id").asString());
|
|
|
|
final private @NotNull JComponent myComponent;
|
|
final private @NotNull Function<? super JComponent, ? extends Rectangle> myScreenBoundsProvider;
|
|
|
|
protected volatile @Nullable JBHiDPIScaledImage myImage;
|
|
|
|
protected volatile @Nullable JBHiDPIScaledImage myPopupImage;
|
|
private volatile boolean myPopupShown = false;
|
|
private volatile @NotNull Rectangle myPopupBounds = new Rectangle();
|
|
protected final Object myPopupMutex = new Object();
|
|
|
|
private volatile @Nullable VolatileImage myVolatileImage;
|
|
protected volatile boolean myContentOutdated = false;
|
|
private volatile @Nullable JBCefCaretListener myCaretListener;
|
|
|
|
// DIP (device independent pixel aka logic pixel) size in screen pixels. Expected to be != 1 only if JRE supports HDPI
|
|
private volatile double myPixelDensity = 1;
|
|
private volatile double myScaleFactor = 1;
|
|
|
|
private final @NotNull AtomicReference<Point> myLocationOnScreenRef = new AtomicReference<>(new Point());
|
|
|
|
JBCefOsrHandler(@NotNull JComponent component, @NotNull Function<? super JComponent, ? extends Rectangle> screenBoundsProvider) {
|
|
myComponent = component;
|
|
myScreenBoundsProvider = screenBoundsProvider;
|
|
}
|
|
|
|
@Override
|
|
public void onPopupShow(CefBrowser browser, boolean show) {
|
|
synchronized (myPopupMutex) {
|
|
myPopupShown = show;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onPopupSize(CefBrowser browser, Rectangle size) {
|
|
synchronized (myPopupMutex) {
|
|
myPopupBounds = size;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onPaint(CefBrowser browser, boolean popup, Rectangle[] dirtyRects, ByteBuffer buffer, int width, int height) {
|
|
myFpsMeter.onPaintStarted();
|
|
JBHiDPIScaledImage image = popup ? myPopupImage : myImage;
|
|
|
|
Dimension size = getRealImageSize(image);
|
|
if (size.width != width || size.height != height) {
|
|
image = (JBHiDPIScaledImage)RetinaImage.createFrom(new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE),
|
|
getPixelDensity(), null);
|
|
}
|
|
|
|
assert image != null;
|
|
if (popup) {
|
|
synchronized (myPopupMutex) {
|
|
drawByteBuffer(image, buffer, dirtyRects);
|
|
myPopupImage = image;
|
|
}
|
|
}
|
|
else {
|
|
drawByteBuffer(image, buffer, dirtyRects);
|
|
myImage = image;
|
|
}
|
|
|
|
myContentOutdated = true;
|
|
SwingUtilities.invokeLater(() -> {
|
|
if (!browser.getUIComponent().isShowing()) return;
|
|
Component component = browser.getUIComponent();
|
|
JRootPane root = SwingUtilities.getRootPane(component);
|
|
RepaintManager rm = RepaintManager.currentManager(root);
|
|
Rectangle dirtySrc = new Rectangle(0, 0, component.getWidth(), component.getHeight());
|
|
Rectangle dirtyDst = SwingUtilities.convertRectangle(component, dirtySrc, root);
|
|
int dx = 1;
|
|
// NOTE: should mark area outside browser (otherwise the background component won't be repainted)
|
|
rm.addDirtyRegion(root, dirtyDst.x - dx, dirtyDst.y - dx, dirtyDst.width + dx * 2, dirtyDst.height + dx * 2);
|
|
});
|
|
|
|
{ // notify fps-meter
|
|
long pixCount = 0;
|
|
for (Rectangle r : dirtyRects)
|
|
pixCount += (long)r.width * r.height;
|
|
myFpsMeter.onPaintFinished(pixCount);
|
|
}
|
|
}
|
|
|
|
protected Dimension getCurrentFrameSize() {
|
|
JBHiDPIScaledImage image = myImage;
|
|
return image == null ? null : new Dimension(image.getWidth(), image.getHeight());
|
|
}
|
|
|
|
public void paint(Graphics2D g) {
|
|
Dimension frameSize = getCurrentFrameSize();
|
|
if (frameSize == null)
|
|
return;
|
|
|
|
myFpsMeter.paintFrameStarted();
|
|
VolatileImage vi = myVolatileImage;
|
|
|
|
do {
|
|
boolean contentOutdated = myContentOutdated;
|
|
myContentOutdated = false;
|
|
if (vi == null || vi.getWidth() != frameSize.width || vi.getHeight() != frameSize.height) {
|
|
vi = createVolatileImage(g, frameSize.width, frameSize.height);
|
|
}
|
|
else if (contentOutdated) {
|
|
drawVolatileImage(vi);
|
|
}
|
|
|
|
switch (vi.validate(g.getDeviceConfiguration())) {
|
|
case VolatileImage.IMAGE_RESTORED -> drawVolatileImage(vi);
|
|
case VolatileImage.IMAGE_INCOMPATIBLE -> vi = createVolatileImage(g, frameSize.width, frameSize.height);
|
|
}
|
|
|
|
g.drawImage(vi, 0, 0, null);
|
|
}
|
|
while (vi.contentsLost());
|
|
|
|
myVolatileImage = vi;
|
|
|
|
if (myPopupShown) {
|
|
synchronized (myPopupMutex) {
|
|
Image popupImage = myPopupImage;
|
|
if (myPopupShown && popupImage != null) {
|
|
UIUtil.drawImage(g, popupImage, myPopupBounds.x, myPopupBounds.y, null);
|
|
}
|
|
}
|
|
}
|
|
|
|
myFpsMeter.paintFrameFinished(g);
|
|
}
|
|
|
|
public void setScreenInfo(double pixelDensity, double scaleFactor) {
|
|
myPixelDensity = pixelDensity;
|
|
myScaleFactor = scaleFactor;
|
|
}
|
|
|
|
protected double getPixelDensity() { return myPixelDensity; }
|
|
|
|
protected double getScaleFactor() { return myScaleFactor; }
|
|
|
|
@Override
|
|
public Rectangle getViewRect(CefBrowser browser) {
|
|
Component component = browser.getUIComponent();
|
|
double scale = getScaleFactor();
|
|
double value = component.getWidth() / scale;
|
|
double value1 = component.getHeight() / scale;
|
|
return new Rectangle(0, 0, (int)Math.ceil(value), (int)Math.ceil(value1));
|
|
}
|
|
|
|
@Override
|
|
public boolean getScreenInfo(CefBrowser browser, CefScreenInfo screenInfo) {
|
|
Rectangle rect = myScreenBoundsProvider.fun(myComponent);
|
|
double scale = myScaleFactor * myPixelDensity;
|
|
screenInfo.Set(scale, 32, 4, false, rect, rect);
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public Point getScreenPoint(CefBrowser browser, Point viewPoint) {
|
|
Point pt = viewPoint.getLocation();
|
|
Point loc = myLocationOnScreenRef.get();
|
|
if (OS.isMacintosh()) {
|
|
Rectangle rect = myScreenBoundsProvider.fun(myComponent);
|
|
pt.setLocation(loc.x + pt.x, rect.height - loc.y - pt.y);
|
|
}
|
|
else {
|
|
pt.translate(loc.x, loc.y);
|
|
}
|
|
return OS.isMacintosh() ? pt : toRealCoordinates(pt);
|
|
}
|
|
|
|
@Override
|
|
public double getDeviceScaleFactor(CefBrowser browser) {
|
|
return myScaleFactor * myPixelDensity;
|
|
}
|
|
|
|
@Override
|
|
public boolean onCursorChange(CefBrowser browser, int cursorType) {
|
|
SwingUtilities.invokeLater(() -> browser.getUIComponent().setCursor(new Cursor(cursorType)));
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean startDragging(CefBrowser browser, CefDragData dragData, int mask, int x, int y) {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public void updateDragCursor(CefBrowser browser, int operation) {
|
|
}
|
|
|
|
@Override
|
|
public void OnImeCompositionRangeChanged(CefBrowser browser, CefRange selectionRange, Rectangle[] characterBounds) {
|
|
JBCefCaretListener listener = myCaretListener;
|
|
if (listener != null) {
|
|
listener.onImeCompositionRangeChanged(selectionRange, characterBounds);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void OnTextSelectionChanged(CefBrowser browser, String selectedText, CefRange selectionRange) {
|
|
JBCefCaretListener listener = myCaretListener;
|
|
if (listener != null) {
|
|
listener.onTextSelectionChanged(selectedText, selectionRange);
|
|
}
|
|
}
|
|
|
|
public void setLocationOnScreen(Point location) {
|
|
myLocationOnScreenRef.set(location);
|
|
}
|
|
|
|
private @NotNull Point toRealCoordinates(@NotNull Point pt) {
|
|
double scale = getPixelDensity();
|
|
return new Point((int)Math.round(pt.x * scale), (int)Math.round(pt.y * scale));
|
|
}
|
|
|
|
void addCaretListener(JBCefCaretListener listener) {
|
|
myCaretListener = listener;
|
|
}
|
|
|
|
private static @NotNull Dimension getRealImageSize(JBHiDPIScaledImage image) {
|
|
if (image == null) return new Dimension(0, 0);
|
|
BufferedImage bi = (BufferedImage)image.getDelegate();
|
|
assert bi != null;
|
|
return new Dimension(bi.getWidth(), bi.getHeight());
|
|
}
|
|
|
|
private static void drawByteBuffer(@NotNull JBHiDPIScaledImage dst, @NotNull ByteBuffer src, Rectangle[] rectangles) {
|
|
BufferedImage image = (BufferedImage)dst.getDelegate();
|
|
assert image != null;
|
|
int[] dstData = ((DataBufferInt)image.getRaster().getDataBuffer()).getData();
|
|
IntBuffer srcData = src.order(ByteOrder.LITTLE_ENDIAN).asIntBuffer();
|
|
for (Rectangle rect : rectangles) {
|
|
if (rect.width < image.getWidth()) {
|
|
for (int line = rect.y; line < rect.y + rect.height; line++) {
|
|
int offset = line * image.getWidth() + rect.x;
|
|
srcData.position(offset).get(dstData, offset, Math.min(rect.width, src.capacity() - offset));
|
|
}
|
|
}
|
|
else { // optimized for a buffer wide dirty rect
|
|
int offset = rect.y * image.getWidth();
|
|
srcData.position(offset).get(dstData, offset, Math.min(rect.height * image.getWidth(), src.capacity() - offset));
|
|
}
|
|
}
|
|
}
|
|
|
|
protected void drawVolatileImage(VolatileImage vi) {
|
|
JBHiDPIScaledImage image = myImage;
|
|
|
|
// Draw the buffered image into VolatileImage
|
|
Graphics2D g = (Graphics2D)vi.getGraphics().create();
|
|
try {
|
|
g.setBackground(Gray.TRANSPARENT);
|
|
g.setComposite(AlphaComposite.Src);
|
|
g.clearRect(0, 0, vi.getWidth(), vi.getHeight());
|
|
|
|
if (image != null) {
|
|
UIUtil.drawImage(g, image, 0, 0, null);
|
|
}
|
|
}
|
|
finally {
|
|
g.dispose();
|
|
}
|
|
}
|
|
|
|
private VolatileImage createVolatileImage(Graphics2D g, int width, int height) {
|
|
VolatileImage image = g.getDeviceConfiguration().createCompatibleVolatileImage(width, height, Transparency.TRANSLUCENT);
|
|
|
|
{// clear image
|
|
Graphics2D gimg = (Graphics2D)image.getGraphics().create();
|
|
gimg.setBackground(Gray.TRANSPARENT);
|
|
gimg.setComposite(AlphaComposite.Src);
|
|
gimg.clearRect(0, 0, image.getWidth(), image.getHeight());
|
|
gimg.dispose();
|
|
}
|
|
|
|
drawVolatileImage(image);
|
|
return image;
|
|
}
|
|
}
|