Browse Source

Indicate preview progress with loading panel

idea-preview-ux-improvements-build
Alexey Tsvetkov 3 years ago
parent
commit
262217980e
  1. 51
      gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewListener.kt
  2. 7
      gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewManager.kt
  3. 11
      gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/RenderedFrame.kt
  4. 3
      idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewPanel.kt
  5. 104
      idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewStateService.kt
  6. 8
      idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewToolWindow.kt
  7. 13
      idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/RunPreviewAction.kt

51
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<PreviewListener>()
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)
}
}

7
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 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 log = PrintStreamLogger("SERVER")
private val previewSocket = newServerSocket() private val previewSocket = newServerSocket()
private val gradleCallbackSocket = 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.get() && frameRequest.get() == null) {
if (shouldRequestFrame.compareAndSet(true, false)) { if (shouldRequestFrame.compareAndSet(true, false)) {
if (frameRequest.compareAndSet(null, request)) { if (frameRequest.compareAndSet(null, request)) {
previewListener.onNewRenderRequest(request)
sendPreviewRequest(classpath, request) sendPreviewRequest(classpath, request)
} else { } else {
shouldRequestFrame.compareAndSet(false, true) shouldRequestFrame.compareAndSet(false, true)
@ -117,7 +120,7 @@ class PreviewManagerImpl(private val onNewFrame: (RenderedFrame) -> Unit) : Prev
frameRequest.get()?.let { request -> frameRequest.get()?.let { request ->
frameRequest.compareAndSet(request, null) frameRequest.compareAndSet(request, null)
} }
onNewFrame(renderedFrame) previewListener.onRenderedFrame(renderedFrame)
} }
} }
} }

11
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 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( data class RenderedFrame(
val bytes: ByteArray, val bytes: ByteArray,
val width: Int, val width: Int,
@ -29,4 +34,10 @@ data class RenderedFrame(
result = 31 * result + height result = 31 * result + height
return result return result
} }
val image: BufferedImage
get() = ByteArrayInputStream(bytes).use { ImageIO.read(it) }
val dimension: Dimension
get() = Dimension(width, height)
} }

3
idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewPanel.kt

@ -37,4 +37,7 @@ internal class PreviewPanel : JPanel() {
repaint() repaint()
} }
override fun getPreferredSize(): Dimension? =
imageDimension ?: super.getPreferredSize()
} }

104
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.Disposable
import com.intellij.openapi.components.Service import com.intellij.openapi.components.Service
import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.FrameConfig import com.intellij.openapi.util.Disposer
import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.PreviewManager import com.intellij.ui.components.JBLoadingPanel
import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.PreviewManagerImpl import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.*
import java.awt.Dimension import java.awt.Dimension
import java.io.ByteArrayInputStream
import javax.imageio.ImageIO
import javax.swing.JComponent import javax.swing.JComponent
import javax.swing.event.AncestorEvent import javax.swing.event.AncestorEvent
import javax.swing.event.AncestorListener import javax.swing.event.AncestorListener
@Service @Service
class PreviewStateService : Disposable { class PreviewStateService : Disposable {
private var myPanel: PreviewPanel? = null private val previewListener = CompositePreviewListener()
private val previewManager: PreviewManager = PreviewManagerImpl { frame -> private val previewManager: PreviewManager = PreviewManagerImpl(previewListener)
ByteArrayInputStream(frame.bytes).use { input ->
val image = ImageIO.read(input)
myPanel?.previewImage(image, Dimension(frame.width, frame.height))
}
}
val gradleCallbackPort: Int val gradleCallbackPort: Int
get() = previewManager.gradleCallbackPort get() = previewManager.gradleCallbackPort
private val myListener = object : AncestorListener { override fun dispose() {
private fun updateFrameSize(c: JComponent) { previewManager.close()
val frameConfig = FrameConfig( }
width = c.width,
height = c.height,
scale = c.graphicsConfiguration.defaultTransform.scaleX
)
previewManager.updateFrameConfig(frameConfig)
}
override fun ancestorAdded(event: AncestorEvent) { internal fun registerPreviewPanels(
updateFrameSize(event.component) 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) { internal fun buildStarted() {
updateFrameSize(event.component) previewListener.onNewBuildRequest()
}
} }
override fun dispose() { internal fun buildFinished(success: Boolean) {
myPanel?.removeAncestorListener(myListener) previewListener.onFinishedBuild(success)
previewManager.close() }
}
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) { override fun ancestorRemoved(event: AncestorEvent) {
myPanel = panel }
panel.addAncestorListener(myListener)
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()
}
}

8
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.project.Project
import com.intellij.openapi.wm.ToolWindow import com.intellij.openapi.wm.ToolWindow
import com.intellij.openapi.wm.ToolWindowFactory import com.intellij.openapi.wm.ToolWindowFactory
import com.intellij.ui.components.JBLoadingPanel
import java.awt.BorderLayout
class PreviewToolWindow : ToolWindowFactory, DumbAware { class PreviewToolWindow : ToolWindowFactory, DumbAware {
override fun isApplicable(project: Project): Boolean { override fun isApplicable(project: Project): Boolean {
@ -23,8 +25,10 @@ class PreviewToolWindow : ToolWindowFactory, DumbAware {
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) { override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
toolWindow.contentManager.let { content -> toolWindow.contentManager.let { content ->
val panel = PreviewPanel() val panel = PreviewPanel()
content.addContent(content.factory.createContent(panel, null, false)) val loadingPanel = JBLoadingPanel(BorderLayout(), project)
project.service<PreviewStateService>().registerPreviewPanel(panel) loadingPanel.add(panel, BorderLayout.CENTER)
content.addContent(content.factory.createContent(loadingPanel, null, false))
project.service<PreviewStateService>().registerPreviewPanels(panel, loadingPanel)
} }
} }

13
idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/RunPreviewAction.kt

@ -35,12 +35,18 @@ class RunPreviewAction(
settings.taskNames = listOf("configureDesktopPreview") settings.taskNames = listOf("configureDesktopPreview")
settings.vmOptions = gradleVmOptions settings.vmOptions = gradleVmOptions
settings.externalSystemIdString = GradleConstants.SYSTEM_ID.id settings.externalSystemIdString = GradleConstants.SYSTEM_ID.id
val gradleCallbackPort = project.service<PreviewStateService>().gradleCallbackPort val previewService = project.service<PreviewStateService>()
val gradleCallbackPort = previewService.gradleCallbackPort
settings.scriptParameters = settings.scriptParameters =
listOf( listOf(
"-Pcompose.desktop.preview.target=$fqName", "-Pcompose.desktop.preview.target=$fqName",
"-Pcompose.desktop.preview.ide.port=$gradleCallbackPort" "-Pcompose.desktop.preview.ide.port=$gradleCallbackPort"
).joinToString(" ") ).joinToString(" ")
SwingUtilities.invokeLater {
ToolWindowManager.getInstance(project).getToolWindow("Desktop Preview")?.activate {
previewService.buildStarted()
}
}
runTask( runTask(
settings, settings,
DefaultRunExecutor.EXECUTOR_ID, DefaultRunExecutor.EXECUTOR_ID,
@ -48,11 +54,10 @@ class RunPreviewAction(
GradleConstants.SYSTEM_ID, GradleConstants.SYSTEM_ID,
object : TaskCallback { object : TaskCallback {
override fun onSuccess() { override fun onSuccess() {
SwingUtilities.invokeLater { previewService.buildFinished(success = true)
ToolWindowManager.getInstance(project).getToolWindow("Desktop Preview")?.activate { }
}
} }
override fun onFailure() { override fun onFailure() {
previewService.buildFinished(success = false)
} }
}, },
ProgressExecutionMode.IN_BACKGROUND_ASYNC, ProgressExecutionMode.IN_BACKGROUND_ASYNC,

Loading…
Cancel
Save