diff --git a/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewListener.kt b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewListener.kt new file mode 100644 index 0000000000..017823df3f --- /dev/null +++ b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewListener.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package org.jetbrains.compose.desktop.ui.tooling.preview.rpc + +interface PreviewListener { + fun onNewBuildRequest() + fun onFinishedBuild(success: Boolean) + fun onNewRenderRequest(previewRequest: FrameRequest) + fun onRenderedFrame(frame: RenderedFrame) +} + +open class PreviewListenerBase : PreviewListener { + override fun onNewBuildRequest() {} + override fun onFinishedBuild(success: Boolean) {} + + override fun onNewRenderRequest(previewRequest: FrameRequest) {} + override fun onRenderedFrame(frame: RenderedFrame) {} +} + +class CompositePreviewListener : PreviewListener { + private val listeners = arrayListOf() + + override fun onNewBuildRequest() { + forEachListener { it.onNewBuildRequest() } + } + + override fun onFinishedBuild(success: Boolean) { + forEachListener { it.onFinishedBuild(success) } + } + + override fun onNewRenderRequest(previewRequest: FrameRequest) { + forEachListener { it.onNewRenderRequest(previewRequest) } + } + + override fun onRenderedFrame(frame: RenderedFrame) { + forEachListener { it.onRenderedFrame(frame) } + } + + @Synchronized + fun addListener(listener: PreviewListener) { + listeners.add(listener) + } + + @Synchronized + private fun forEachListener(fn: (PreviewListener) -> Unit) { + listeners.forEach(fn) + } +} diff --git a/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewManager.kt b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewManager.kt index 7ceeae5aeb..6b3873b108 100644 --- a/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewManager.kt +++ b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewManager.kt @@ -47,7 +47,9 @@ private data class RunningPreview( get() = connection.isAlive && process.isAlive } -class PreviewManagerImpl(private val onNewFrame: (RenderedFrame) -> Unit) : PreviewManager { +class PreviewManagerImpl( + private val previewListener: PreviewListener = PreviewListenerBase() +) : PreviewManager { private val log = PrintStreamLogger("SERVER") private val previewSocket = newServerSocket() private val gradleCallbackSocket = newServerSocket() @@ -101,6 +103,7 @@ class PreviewManagerImpl(private val onNewFrame: (RenderedFrame) -> Unit) : Prev if (shouldRequestFrame.get() && frameRequest.get() == null) { if (shouldRequestFrame.compareAndSet(true, false)) { if (frameRequest.compareAndSet(null, request)) { + previewListener.onNewRenderRequest(request) sendPreviewRequest(classpath, request) } else { shouldRequestFrame.compareAndSet(false, true) @@ -117,7 +120,7 @@ class PreviewManagerImpl(private val onNewFrame: (RenderedFrame) -> Unit) : Prev frameRequest.get()?.let { request -> frameRequest.compareAndSet(request, null) } - onNewFrame(renderedFrame) + previewListener.onRenderedFrame(renderedFrame) } } } diff --git a/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/RenderedFrame.kt b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/RenderedFrame.kt index 838c7f9cfe..93c04aa5c4 100644 --- a/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/RenderedFrame.kt +++ b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/RenderedFrame.kt @@ -5,6 +5,11 @@ package org.jetbrains.compose.desktop.ui.tooling.preview.rpc +import java.awt.Dimension +import java.awt.image.BufferedImage +import java.io.ByteArrayInputStream +import javax.imageio.ImageIO + data class RenderedFrame( val bytes: ByteArray, val width: Int, @@ -29,4 +34,10 @@ data class RenderedFrame( result = 31 * result + height return result } + + val image: BufferedImage + get() = ByteArrayInputStream(bytes).use { ImageIO.read(it) } + + val dimension: Dimension + get() = Dimension(width, height) } \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewPanel.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewPanel.kt index ce440cb78c..0087a2d610 100644 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewPanel.kt +++ b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewPanel.kt @@ -37,4 +37,7 @@ internal class PreviewPanel : JPanel() { repaint() } + + override fun getPreferredSize(): Dimension? = + imageDimension ?: super.getPreferredSize() } \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewStateService.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewStateService.kt index 0eb5291af5..dd8652f0f0 100644 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewStateService.kt +++ b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewStateService.kt @@ -7,57 +7,91 @@ package org.jetbrains.compose.desktop.ide.preview import com.intellij.openapi.Disposable import com.intellij.openapi.components.Service -import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.FrameConfig -import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.PreviewManager -import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.PreviewManagerImpl +import com.intellij.openapi.util.Disposer +import com.intellij.ui.components.JBLoadingPanel +import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.* import java.awt.Dimension -import java.io.ByteArrayInputStream -import javax.imageio.ImageIO import javax.swing.JComponent import javax.swing.event.AncestorEvent import javax.swing.event.AncestorListener @Service class PreviewStateService : Disposable { - private var myPanel: PreviewPanel? = null - private val previewManager: PreviewManager = PreviewManagerImpl { frame -> - ByteArrayInputStream(frame.bytes).use { input -> - val image = ImageIO.read(input) - myPanel?.previewImage(image, Dimension(frame.width, frame.height)) - } - } + private val previewListener = CompositePreviewListener() + private val previewManager: PreviewManager = PreviewManagerImpl(previewListener) val gradleCallbackPort: Int get() = previewManager.gradleCallbackPort - private val myListener = object : AncestorListener { - private fun updateFrameSize(c: JComponent) { - val frameConfig = FrameConfig( - width = c.width, - height = c.height, - scale = c.graphicsConfiguration.defaultTransform.scaleX - ) - previewManager.updateFrameConfig(frameConfig) - } + override fun dispose() { + previewManager.close() + } - override fun ancestorAdded(event: AncestorEvent) { - updateFrameSize(event.component) - } + internal fun registerPreviewPanels( + previewPanel: PreviewPanel, + loadingPanel: JBLoadingPanel + ) { + val previewResizeListener = PreviewResizeListener(previewManager) + previewPanel.addAncestorListener(previewResizeListener) + Disposer.register(this) { previewPanel.removeAncestorListener(previewResizeListener) } - override fun ancestorRemoved(event: AncestorEvent) { - } + previewListener.addListener(PreviewPanelUpdater(previewPanel)) + previewListener.addListener(LoadingPanelUpdater(loadingPanel)) + } - override fun ancestorMoved(event: AncestorEvent) { - updateFrameSize(event.component) - } + internal fun buildStarted() { + previewListener.onNewBuildRequest() } - override fun dispose() { - myPanel?.removeAncestorListener(myListener) - previewManager.close() + internal fun buildFinished(success: Boolean) { + previewListener.onFinishedBuild(success) + } +} + +private class PreviewResizeListener(private val previewManager: PreviewManager) : AncestorListener { + private fun updateFrameSize(c: JComponent) { + val frameConfig = FrameConfig( + width = c.width, + height = c.height, + scale = c.graphicsConfiguration.defaultTransform.scaleX + ) + previewManager.updateFrameConfig(frameConfig) + } + + override fun ancestorAdded(event: AncestorEvent) { + updateFrameSize(event.component) + } - internal fun registerPreviewPanel(panel: PreviewPanel) { - myPanel = panel - panel.addAncestorListener(myListener) + override fun ancestorRemoved(event: AncestorEvent) { + } + + override fun ancestorMoved(event: AncestorEvent) { + updateFrameSize(event.component) } } + +private class PreviewPanelUpdater(private val panel: PreviewPanel) : PreviewListenerBase() { + override fun onRenderedFrame(frame: RenderedFrame) { + panel.previewImage(frame.image, frame.dimension) + } +} + +private class LoadingPanelUpdater(private val panel: JBLoadingPanel) : PreviewListenerBase() { + override fun onNewBuildRequest() { + panel.setLoadingText("Building project") + panel.startLoading() + } + + override fun onFinishedBuild(success: Boolean) { + panel.stopLoading() + } + + override fun onNewRenderRequest(previewRequest: FrameRequest) { + panel.setLoadingText("Rendering preview") + panel.startLoading() + } + + override fun onRenderedFrame(frame: RenderedFrame) { + panel.stopLoading() + } +} \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewToolWindow.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewToolWindow.kt index 4f08d278c4..b75fd4f6b6 100644 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewToolWindow.kt +++ b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewToolWindow.kt @@ -9,6 +9,8 @@ import com.intellij.openapi.project.DumbAware import com.intellij.openapi.project.Project import com.intellij.openapi.wm.ToolWindow import com.intellij.openapi.wm.ToolWindowFactory +import com.intellij.ui.components.JBLoadingPanel +import java.awt.BorderLayout class PreviewToolWindow : ToolWindowFactory, DumbAware { override fun isApplicable(project: Project): Boolean { @@ -23,8 +25,10 @@ class PreviewToolWindow : ToolWindowFactory, DumbAware { override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) { toolWindow.contentManager.let { content -> val panel = PreviewPanel() - content.addContent(content.factory.createContent(panel, null, false)) - project.service().registerPreviewPanel(panel) + val loadingPanel = JBLoadingPanel(BorderLayout(), project) + loadingPanel.add(panel, BorderLayout.CENTER) + content.addContent(content.factory.createContent(loadingPanel, null, false)) + project.service().registerPreviewPanels(panel, loadingPanel) } } diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/RunPreviewAction.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/RunPreviewAction.kt index ea6f26d668..566da69f4c 100644 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/RunPreviewAction.kt +++ b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/RunPreviewAction.kt @@ -35,12 +35,18 @@ class RunPreviewAction( settings.taskNames = listOf("configureDesktopPreview") settings.vmOptions = gradleVmOptions settings.externalSystemIdString = GradleConstants.SYSTEM_ID.id - val gradleCallbackPort = project.service().gradleCallbackPort + val previewService = project.service() + val gradleCallbackPort = previewService.gradleCallbackPort settings.scriptParameters = listOf( "-Pcompose.desktop.preview.target=$fqName", "-Pcompose.desktop.preview.ide.port=$gradleCallbackPort" ).joinToString(" ") + SwingUtilities.invokeLater { + ToolWindowManager.getInstance(project).getToolWindow("Desktop Preview")?.activate { + previewService.buildStarted() + } + } runTask( settings, DefaultRunExecutor.EXECUTOR_ID, @@ -48,11 +54,10 @@ class RunPreviewAction( GradleConstants.SYSTEM_ID, object : TaskCallback { override fun onSuccess() { - SwingUtilities.invokeLater { - ToolWindowManager.getInstance(project).getToolWindow("Desktop Preview")?.activate { } - } + previewService.buildFinished(success = true) } override fun onFailure() { + previewService.buildFinished(success = false) } }, ProgressExecutionMode.IN_BACKGROUND_ASYNC,