From 63e9ac9936997a50455643c8649b39e93608e91f Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Wed, 6 Jun 2018 14:22:40 +0300 Subject: [PATCH] extract JavaFxHtml panel into platform --- .../intellij/ui/javafx/JFXPanelWrapper.java | 53 ++++++++ .../intellij/ui/javafx/JavaFxHtmlPanel.java | 113 ++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 platform/platform-impl/src/com/intellij/ui/javafx/JFXPanelWrapper.java create mode 100644 platform/platform-impl/src/com/intellij/ui/javafx/JavaFxHtmlPanel.java diff --git a/platform/platform-impl/src/com/intellij/ui/javafx/JFXPanelWrapper.java b/platform/platform-impl/src/com/intellij/ui/javafx/JFXPanelWrapper.java new file mode 100644 index 000000000000..ebea56cc2d34 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ui/javafx/JFXPanelWrapper.java @@ -0,0 +1,53 @@ +// Copyright 2000-2018 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.javafx; + +import com.intellij.util.FieldAccessor; +import com.intellij.util.ui.JBUI; +import com.intellij.util.ui.UIUtil; +import com.sun.javafx.embed.EmbeddedSceneInterface; +import com.sun.javafx.tk.TKScene; +import javafx.application.Platform; +import javafx.embed.swing.JFXPanel; +import javafx.scene.Scene; + +import java.awt.*; + +public class JFXPanelWrapper extends JFXPanel { + private static final FieldAccessor myScaleFactorAccessor = new FieldAccessor<>(JFXPanel.class, "scaleFactor"); + + public JFXPanelWrapper() { + Platform.setImplicitExit(false); + } + + /** + * This override fixes the situation of using multiple JFXPanels + * with jbtabs/splitters when some of them are not showing. + * On getMinimumSize there is no layout manager nor peer so + * the result could be #size() which is incorrect. + * @return zero size + */ + @Override + public Dimension getMinimumSize() { + return new Dimension(0, 0); + } + + @Override + public void addNotify() { + super.addNotify(); + if (UIUtil.isJreHiDPIEnabled()) { + // JFXPanel is scaled asynchronously after first repaint, what may lead + // to showing unscaled content. To work it around, set "scaleFactor" ahead. + int scale = Math.round(JBUI.sysScale(this)); + myScaleFactorAccessor.set(this, scale); + Scene scene = getScene(); + // If scene is null then it will be set later and super.setEmbeddedScene(..) will init its scale properly, + // otherwise explicitly set scene scale to match JFXPanel.scaleFactor. + if (scene != null) { + TKScene tks = scene.impl_getPeer(); + if (tks instanceof EmbeddedSceneInterface) { + ((EmbeddedSceneInterface)tks).setPixelScaleFactor(scale); + } + } + } + } +} diff --git a/platform/platform-impl/src/com/intellij/ui/javafx/JavaFxHtmlPanel.java b/platform/platform-impl/src/com/intellij/ui/javafx/JavaFxHtmlPanel.java new file mode 100644 index 000000000000..a06b66bfb0a5 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ui/javafx/JavaFxHtmlPanel.java @@ -0,0 +1,113 @@ +// Copyright 2000-2018 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.javafx; + +import com.intellij.ide.IdeEventQueue; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.ui.JBColor; +import com.intellij.util.ui.JBUI; +import com.sun.javafx.application.PlatformImpl; +import javafx.application.Platform; +import javafx.embed.swing.JFXPanel; +import javafx.scene.Scene; +import javafx.scene.web.WebEngine; +import javafx.scene.web.WebView; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + +public class JavaFxHtmlPanel implements Disposable { + @NotNull + private final JPanel myPanelWrapper; + @NotNull + private final List myInitActions = new ArrayList<>(); + @Nullable + private JFXPanel myPanel; + @Nullable protected WebView myWebView; + + public JavaFxHtmlPanel() { + myPanelWrapper = new JPanel(new BorderLayout()); + myPanelWrapper.setBackground(JBColor.background()); + + ApplicationManager.getApplication().invokeLater(() -> runFX(() -> PlatformImpl.startup(() -> { + myWebView = new WebView(); + myWebView.setContextMenuEnabled(false); + myWebView.setZoom(JBUI.scale(1.f)); + + final WebEngine engine = myWebView.getEngine(); + registerListeners(engine); + + final Scene scene = new Scene(myWebView); + + ApplicationManager.getApplication().invokeLater(() -> runFX(() -> { + myPanel = new JFXPanelWrapper(); + + Platform.runLater(() -> myPanel.setScene(scene)); + + setHtml(""); + for (Runnable action : myInitActions) { + Platform.runLater(action); + } + myInitActions.clear(); + + myPanelWrapper.add(myPanel, BorderLayout.CENTER); + myPanelWrapper.repaint(); + })); + }))); + } + + protected void registerListeners(@NotNull WebEngine engine) { + } + + private static void runFX(@NotNull Runnable r) { + IdeEventQueue.unsafeNonblockingExecute(r); + } + + protected void runInPlatformWhenAvailable(@NotNull Runnable runnable) { + ApplicationManager.getApplication().assertIsDispatchThread(); + if (myPanel == null) { + myInitActions.add(runnable); + } + else { + Platform.runLater(runnable); + } + } + + @NotNull + public JComponent getComponent() { + return myPanelWrapper; + } + + public void setHtml(@NotNull String html) { + final String htmlToRender = prepareHtml(html); + runInPlatformWhenAvailable(() -> getWebViewGuaranteed().getEngine().loadContent(htmlToRender)); + } + + @NotNull + protected String prepareHtml(@NotNull String html) { + return html; + } + + public void render() { + runInPlatformWhenAvailable(() -> { + getWebViewGuaranteed().getEngine().reload(); + ApplicationManager.getApplication().invokeLater(myPanelWrapper::repaint); + }); + } + + @Override + public void dispose() { + } + + @NotNull + protected WebView getWebViewGuaranteed() { + if (myWebView == null) { + throw new IllegalStateException("WebView should be initialized by now. Check the caller thread"); + } + return myWebView; + } +}