mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IJPL-176416 New Icons API - fading animated icons, improved interop with old animated icons
(cherry picked from commit 9443723cd2b5839d5ebe0e314cafbe8fc55c58e1) (cherry picked from commit e94b7edd8e749e93f650b2550da1335123563e5a) IJ-MR-211495 GitOrigin-RevId: 2f8e88398a6f5a12afd3d75fc492c89d3523457a
This commit is contained in:
committed by
intellij-monorepo-bot
parent
bfb524d7df
commit
b9a82bf5c1
@@ -55,6 +55,7 @@ f:com.intellij.platform.icons.design.IconAlign$Companion
|
||||
f:com.intellij.platform.icons.design.IconAlignKt
|
||||
- sf:iconAlign(com.intellij.platform.icons.design.IconVerticalAlign,com.intellij.platform.icons.design.IconHorizontalAlign):com.intellij.platform.icons.design.IconAlign
|
||||
com.intellij.platform.icons.design.IconAnimationDesigner
|
||||
- a:frame(J,J,J,kotlin.jvm.functions.Function1):V
|
||||
- a:frame(J,kotlin.jvm.functions.Function1):V
|
||||
com.intellij.platform.icons.design.IconDesigner
|
||||
- a:animation(com.intellij.platform.icons.modifiers.IconModifier,kotlin.jvm.functions.Function1):V
|
||||
|
||||
+2
@@ -16,6 +16,7 @@ interface LayerPaintingContext {
|
||||
val slotWidth: Int?
|
||||
val slotHeight: Int?
|
||||
val scaling: ScalingContext
|
||||
val alpha: Float?
|
||||
|
||||
fun drawImage(
|
||||
image: ImageResource,
|
||||
@@ -49,6 +50,7 @@ interface LayerPaintingContext {
|
||||
slotWidth: Int? = null,
|
||||
slotHeight: Int? = null,
|
||||
scale: Float = 1f,
|
||||
alpha: Float = 1f,
|
||||
overrideColorFilter: ColorFilter? = null,
|
||||
): LayerPaintingContext
|
||||
}
|
||||
|
||||
@@ -40,4 +40,5 @@ fun IconDesigner.badge(
|
||||
|
||||
interface IconAnimationDesigner {
|
||||
fun frame(duration: Long, builder: IconDesigner.() -> Unit)
|
||||
fun frame(fadeIn: Long, stay: Long, fadeOut: Long, builder: IconDesigner.() -> Unit)
|
||||
}
|
||||
|
||||
@@ -82,9 +82,12 @@ com.intellij.platform.icons.impl.DeferredIconResolver
|
||||
- a:resolve(kotlin.coroutines.Continuation):java.lang.Object
|
||||
f:com.intellij.platform.icons.impl.IconAnimationFrame
|
||||
- sf:Companion:com.intellij.platform.icons.impl.IconAnimationFrame$Companion
|
||||
- <init>(java.util.List,J):V
|
||||
- <init>(java.util.List,J,J,J):V
|
||||
- b:<init>(java.util.List,J,J,J,I,kotlin.jvm.internal.DefaultConstructorMarker):V
|
||||
- equals(java.lang.Object):Z
|
||||
- f:getDuration():J
|
||||
- f:getFadeIn():J
|
||||
- f:getFadeOut():J
|
||||
- f:getLayers():java.util.List
|
||||
- hashCode():I
|
||||
bf:com.intellij.platform.icons.impl.IconAnimationFrame$$serializer
|
||||
@@ -226,6 +229,7 @@ f:com.intellij.platform.icons.impl.design.DefaultIconAnimationDesigner
|
||||
- com.intellij.platform.icons.design.IconAnimationDesigner
|
||||
- <init>(com.intellij.platform.icons.impl.design.DefaultIconDesigner):V
|
||||
- f:build():java.util.List
|
||||
- frame(J,J,J,kotlin.jvm.functions.Function1):V
|
||||
- frame(J,kotlin.jvm.functions.Function1):V
|
||||
- f:getRootDesigner():com.intellij.platform.icons.impl.design.DefaultIconDesigner
|
||||
a:com.intellij.platform.icons.impl.design.DefaultIconDesigner
|
||||
@@ -814,6 +818,11 @@ f:com.intellij.platform.icons.impl.rendering.ResolvedScalingContext
|
||||
- f:getFinalDimensions():com.intellij.platform.icons.rendering.Dimensions
|
||||
f:com.intellij.platform.icons.impl.rendering.ScalingKt
|
||||
- sf:resolve(com.intellij.platform.icons.rendering.IconRenderer,F,com.intellij.platform.icons.scale.IconScale):com.intellij.platform.icons.impl.rendering.ResolvedScalingContext
|
||||
com.intellij.platform.icons.impl.rendering.layers.AnimatedIconFrameRenderer
|
||||
- a:getDuration():J
|
||||
- a:getLayout():com.intellij.platform.icons.impl.rendering.layers.LayerLayout
|
||||
- a:getRefreshDuration():J
|
||||
- a:render(com.intellij.platform.icons.rendering.LayerPaintingContext,J):V
|
||||
f:com.intellij.platform.icons.impl.rendering.layers.AnimatedIconLayerRenderer
|
||||
- com.intellij.platform.icons.impl.rendering.layers.IconLayerRenderer
|
||||
- <init>(com.intellij.platform.icons.impl.layers.AnimatedIconLayer,com.intellij.platform.icons.impl.rendering.DefaultRenderingContext):V
|
||||
|
||||
@@ -2,6 +2,11 @@ load("@rules_jvm//:jvm.bzl", "jvm_library", "jvm_provided_library")
|
||||
|
||||
### auto-generated section `build intellij.platform.icons.impl.intellij` start
|
||||
|
||||
jvm_provided_library(
|
||||
name = "platform_core-ui_provided",
|
||||
lib = "//platform/core-ui",
|
||||
)
|
||||
|
||||
jvm_provided_library(
|
||||
name = "platform_icons-api_provided",
|
||||
lib = "//platform/icons-api",
|
||||
@@ -38,8 +43,8 @@ jvm_provided_library(
|
||||
)
|
||||
|
||||
jvm_provided_library(
|
||||
name = "platform_core-ui_provided",
|
||||
lib = "//platform/core-ui",
|
||||
name = "platform_ide-core_provided",
|
||||
lib = "//platform/ide-core",
|
||||
)
|
||||
|
||||
jvm_provided_library(
|
||||
@@ -75,6 +80,7 @@ jvm_library(
|
||||
"//libraries/kotlinx/coroutines/core",
|
||||
"//libraries/kotlinx/serialization/core",
|
||||
"@lib//:jetbrains-annotations",
|
||||
":platform_core-ui_provided",
|
||||
":platform_icons-api_provided",
|
||||
":platform_icons-api_rendering_provided",
|
||||
":platform_icons-impl_provided",
|
||||
@@ -82,7 +88,7 @@ jvm_library(
|
||||
":platform_util-ui_provided",
|
||||
":platform_core_provided",
|
||||
":platform_core-impl_provided",
|
||||
":platform_core-ui_provided",
|
||||
":platform_ide-core_provided",
|
||||
":platform_ide-impl_provided",
|
||||
],
|
||||
)
|
||||
@@ -108,6 +114,8 @@ jvm_library(
|
||||
runtime_deps = [
|
||||
# do not sort,
|
||||
":intellij",
|
||||
"//platform/core-ui",
|
||||
"//platform/core-ui:core-ui_test_lib",
|
||||
"//libraries/kotlinx/coroutines/core:core_test_lib",
|
||||
"//libraries/kotlinx/serialization/core:core_test_lib",
|
||||
"//platform/icons-api",
|
||||
@@ -124,8 +132,8 @@ jvm_library(
|
||||
"//platform/core-api:core_test_lib",
|
||||
"//platform/core-impl",
|
||||
"//platform/core-impl:core-impl_test_lib",
|
||||
"//platform/core-ui",
|
||||
"//platform/core-ui:core-ui_test_lib",
|
||||
"//platform/ide-core",
|
||||
"//platform/ide-core:ide-core_test_lib",
|
||||
"//platform/platform-impl:ide-impl",
|
||||
"//platform/platform-impl:ide-impl_test_lib",
|
||||
],
|
||||
|
||||
@@ -98,12 +98,13 @@ f:com.intellij.platform.icons.impl.intellij.rendering.SwingIconLayerRenderer
|
||||
- setImage(com.intellij.platform.icons.rendering.ImageResource):V
|
||||
- setLayout(com.intellij.platform.icons.impl.rendering.layers.LayerLayout):V
|
||||
f:com.intellij.platform.icons.impl.intellij.rendering.SwingLayerPaintingContext
|
||||
- <init>(java.awt.Component,java.awt.Graphics,I,I,java.lang.Integer,java.lang.Integer,java.lang.Integer,java.lang.Integer,com.intellij.platform.icons.filters.ColorFilter,com.intellij.platform.icons.rendering.ScalingContext):V
|
||||
- b:<init>(java.awt.Component,java.awt.Graphics,I,I,java.lang.Integer,java.lang.Integer,java.lang.Integer,java.lang.Integer,com.intellij.platform.icons.filters.ColorFilter,com.intellij.platform.icons.rendering.ScalingContext,I,kotlin.jvm.internal.DefaultConstructorMarker):V
|
||||
- createNestedLayer(java.lang.Integer,java.lang.Integer,java.lang.Integer,java.lang.Integer,F,com.intellij.platform.icons.filters.ColorFilter):com.intellij.platform.icons.rendering.LayerPaintingContext
|
||||
- <init>(java.awt.Component,java.awt.Graphics,I,I,java.lang.Integer,java.lang.Integer,java.lang.Integer,java.lang.Integer,com.intellij.platform.icons.filters.ColorFilter,F,com.intellij.platform.icons.rendering.ScalingContext):V
|
||||
- b:<init>(java.awt.Component,java.awt.Graphics,I,I,java.lang.Integer,java.lang.Integer,java.lang.Integer,java.lang.Integer,com.intellij.platform.icons.filters.ColorFilter,F,com.intellij.platform.icons.rendering.ScalingContext,I,kotlin.jvm.internal.DefaultConstructorMarker):V
|
||||
- createNestedLayer(java.lang.Integer,java.lang.Integer,java.lang.Integer,java.lang.Integer,F,F,com.intellij.platform.icons.filters.ColorFilter):com.intellij.platform.icons.rendering.LayerPaintingContext
|
||||
- drawCircle(com.intellij.platform.icons.design.Color,I,I,F,F,com.intellij.platform.icons.rendering.DrawMode):V
|
||||
- drawImage(com.intellij.platform.icons.rendering.ImageResource,I,I,java.lang.Integer,java.lang.Integer,I,I,java.lang.Integer,java.lang.Integer,F,com.intellij.platform.icons.filters.ColorFilter):V
|
||||
- drawRect(com.intellij.platform.icons.design.Color,I,I,I,I,F,com.intellij.platform.icons.rendering.DrawMode):V
|
||||
- getAlpha():java.lang.Float
|
||||
- f:getC():java.awt.Component
|
||||
- f:getG():java.awt.Graphics
|
||||
- getOffsetX():I
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="library" name="kotlin-stdlib" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.core.ui" scope="PROVIDED" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="module" module-name="intellij.libraries.kotlinx.coroutines.core" />
|
||||
@@ -39,7 +40,7 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.util.ui" scope="PROVIDED" />
|
||||
<orderEntry type="module" module-name="intellij.platform.core" scope="PROVIDED" />
|
||||
<orderEntry type="module" module-name="intellij.platform.core.impl" scope="PROVIDED" />
|
||||
<orderEntry type="module" module-name="intellij.platform.core.ui" scope="PROVIDED" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ide.core" scope="PROVIDED" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ide.impl" scope="PROVIDED" />
|
||||
<orderEntry type="library" name="jetbrains-annotations" level="project" />
|
||||
</component>
|
||||
|
||||
+25
-11
@@ -25,6 +25,7 @@ import com.intellij.platform.icons.modifiers.scale
|
||||
import com.intellij.platform.icons.rendering.IconRendererManager
|
||||
import com.intellij.platform.icons.scale.IconScale
|
||||
import com.intellij.platform.icons.swing.ScalableSwingIcon
|
||||
import com.intellij.ui.AnimatedIcon
|
||||
import com.intellij.util.messages.Topic
|
||||
import com.intellij.util.messages.Topic.BroadcastDirection
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -74,18 +75,9 @@ class IntelliJIconManager : DefaultIconManager() {
|
||||
override fun toNewIcon(swingIcon: javax.swing.Icon): Icon {
|
||||
if (swingIcon is SwingIcon && swingIcon.scale == null) return swingIcon.icon
|
||||
return if (swingIcon is SwingIcon) {
|
||||
@Suppress("KotlinConstantConditions")
|
||||
if (swingIcon.scale != null) {
|
||||
icon {
|
||||
addSwingLayer(
|
||||
this,
|
||||
swingIcon,
|
||||
IconModifier.scale(swingIcon.scale)
|
||||
)
|
||||
}
|
||||
} else swingIcon.icon
|
||||
unpackSwingIcon(swingIcon)
|
||||
} else {
|
||||
icon {
|
||||
convertLegacyIcon(swingIcon) ?: icon {
|
||||
addSwingLayer(
|
||||
this,
|
||||
swingIcon,
|
||||
@@ -95,6 +87,28 @@ class IntelliJIconManager : DefaultIconManager() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertLegacyIcon(swingIcon: javax.swing.Icon): Icon? {
|
||||
return when (swingIcon) {
|
||||
is AnimatedIcon -> {
|
||||
swingIcon.extractAsNewIcon()
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun unpackSwingIcon(swingIcon: SwingIcon): Icon {
|
||||
@Suppress("KotlinConstantConditions")
|
||||
return if (swingIcon.scale != null) {
|
||||
icon {
|
||||
addSwingLayer(
|
||||
this,
|
||||
swingIcon,
|
||||
IconModifier.scale(swingIcon.scale)
|
||||
)
|
||||
}
|
||||
} else swingIcon.icon
|
||||
}
|
||||
|
||||
override fun markDeferredIconUnused(id: IconIdentifier) {
|
||||
// TODO delete unused deferred icons
|
||||
}
|
||||
|
||||
+11
-4
@@ -34,6 +34,7 @@ class SwingLayerPaintingContext(
|
||||
override val slotHeight: Int? = null,
|
||||
override val slotWidth: Int? = null,
|
||||
private val overrideColorFilter: ColorFilter? = null,
|
||||
override val alpha: Float = 1f,
|
||||
override val scaling: ScalingContext = DefaultScalingContext(1f, 1f),
|
||||
) : LayerPaintingContext {
|
||||
override val offsetX: Int = customX ?: x
|
||||
@@ -45,6 +46,7 @@ class SwingLayerPaintingContext(
|
||||
slotWidth: Int?,
|
||||
slotHeight: Int?,
|
||||
scale: Float,
|
||||
alpha: Float,
|
||||
overrideColorFilter: ColorFilter?
|
||||
): LayerPaintingContext {
|
||||
return SwingLayerPaintingContext(
|
||||
@@ -57,6 +59,7 @@ class SwingLayerPaintingContext(
|
||||
slotHeight,
|
||||
slotWidth,
|
||||
overrideColorFilter ?: this.overrideColorFilter,
|
||||
alpha,
|
||||
DefaultScalingContext(scaling.displayDensity, scaling.contextScale * scale)
|
||||
)
|
||||
}
|
||||
@@ -72,7 +75,7 @@ class SwingLayerPaintingContext(
|
||||
|
||||
@Suppress("unused")
|
||||
private fun drawShape(color: Color, shape: Shape, alpha: Float, mode: DrawMode) {
|
||||
setDrawingDefaults()
|
||||
setDrawingDefaults(alpha)
|
||||
if (g is Graphics2D) {
|
||||
val oldComposite = g.composite
|
||||
val oldPaint = g.paint
|
||||
@@ -118,10 +121,14 @@ class SwingLayerPaintingContext(
|
||||
}
|
||||
}
|
||||
|
||||
private fun setDrawingDefaults() {
|
||||
private fun setDrawingDefaults(alpha: Float) {
|
||||
if (g !is Graphics2D) return
|
||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
|
||||
g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE)
|
||||
val realAlpha = alpha * this.alpha
|
||||
if (realAlpha < 1f) {
|
||||
g.composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, realAlpha)
|
||||
}
|
||||
}
|
||||
|
||||
private fun drawImage(
|
||||
@@ -188,13 +195,13 @@ class SwingLayerPaintingContext(
|
||||
alpha: Float,
|
||||
colorFilter: ColorFilter?,
|
||||
) {
|
||||
// TODO apply alpha & color filters
|
||||
// TODO apply color filters
|
||||
|
||||
val imageWidth = image.getWidth(null)
|
||||
val imageHeight = image.getHeight(null)
|
||||
|
||||
if (imageWidth == 0 || imageHeight == 0) return
|
||||
setDrawingDefaults()
|
||||
setDrawingDefaults(alpha)
|
||||
g.drawImage(
|
||||
image,
|
||||
x,
|
||||
|
||||
+4
-1
@@ -176,6 +176,7 @@ class TestPaintingContext(
|
||||
override val offsetY: Int = 0,
|
||||
override val slotWidth: Int? = null,
|
||||
override val slotHeight: Int? = null,
|
||||
override val alpha: Float = 1f
|
||||
) : LayerPaintingContext {
|
||||
override fun drawImage(
|
||||
image: ImageResource,
|
||||
@@ -222,6 +223,7 @@ class TestPaintingContext(
|
||||
slotWidth: Int?,
|
||||
slotHeight: Int?,
|
||||
scale: Float,
|
||||
alpha: Float,
|
||||
overrideColorFilter: ColorFilter?,
|
||||
): LayerPaintingContext {
|
||||
return TestPaintingContext(
|
||||
@@ -230,7 +232,8 @@ class TestPaintingContext(
|
||||
x ?: offsetX,
|
||||
y ?: offsetY,
|
||||
slotWidth,
|
||||
slotHeight
|
||||
slotHeight,
|
||||
alpha
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import com.intellij.platform.icons.layers.IconLayer
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class IconAnimationFrame(val layers: List<IconLayer>, val duration: Long) {
|
||||
class IconAnimationFrame(val layers: List<IconLayer>, val duration: Long, val fadeIn: Long = 0L, val fadeOut: Long = 0L) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
@@ -25,4 +25,4 @@ class IconAnimationFrame(val layers: List<IconLayer>, val duration: Long) {
|
||||
}
|
||||
|
||||
override fun toString(): String = "IconAnimationFrame(duration=$duration, layers=$layers)"
|
||||
}
|
||||
}
|
||||
+6
@@ -14,5 +14,11 @@ class DefaultIconAnimationDesigner(val rootDesigner: DefaultIconDesigner) : Icon
|
||||
frames.add(IconAnimationFrame(designer.buildLayers(), duration))
|
||||
}
|
||||
|
||||
override fun frame(fadeIn: Long, stay: Long, fadeOut: Long, builder: IconDesigner.() -> Unit) {
|
||||
val designer = rootDesigner.createNestedDesigner()
|
||||
designer.builder()
|
||||
frames.add(IconAnimationFrame(designer.buildLayers(), stay, fadeIn, fadeOut))
|
||||
}
|
||||
|
||||
fun build(): List<IconAnimationFrame> = frames
|
||||
}
|
||||
|
||||
+143
-67
@@ -7,82 +7,158 @@ import com.intellij.platform.icons.impl.layers.AnimatedIconLayer
|
||||
import com.intellij.platform.icons.impl.rendering.DefaultRenderingContext
|
||||
import com.intellij.platform.icons.rendering.LayerPaintingContext
|
||||
import com.intellij.platform.icons.rendering.RenderingContext
|
||||
import kotlin.math.cos
|
||||
|
||||
class AnimatedIconLayerRenderer(
|
||||
private val layer: AnimatedIconLayer,
|
||||
private val renderingContext: DefaultRenderingContext,
|
||||
private val layer: AnimatedIconLayer,
|
||||
private val renderingContext: DefaultRenderingContext,
|
||||
) : IconLayerRenderer {
|
||||
private val currentContext = renderingContext.adjustTo(layer)
|
||||
private val frameRenderers = layer.frames.map { AnimatedIconFrameRenderer(it, currentContext) }
|
||||
private var lastFrame = FrameData(-1, 0, 0)
|
||||
private val currentContext = renderingContext.adjustTo(layer)
|
||||
private val frameRenderers = createFrameRenderers()
|
||||
private var lastFrame = FrameData(-1, 0, 0, 0)
|
||||
|
||||
override val layout: LayerLayout = applyLayout()
|
||||
override val layout: LayerLayout = applyLayout()
|
||||
|
||||
private fun applyLayout(): LayerLayout {
|
||||
val dimensions = frameRenderers.maxCompoundSize { it.layout.consumedSpace() }
|
||||
return applyLayout(layer.modifier, dimensions.width, dimensions.height)
|
||||
private fun createFrameRenderers(): List<AnimatedIconFrameRenderer> {
|
||||
val renderers = mutableListOf<AnimatedIconFrameRenderer>()
|
||||
for (frame in layer.frames) {
|
||||
val base = BaseAnimatedIconFrameRenderer(frame, currentContext)
|
||||
if (frame.fadeIn > 0L) {
|
||||
renderers.add(FadingAnimatedIconFrameRenderer(base, frame.fadeIn, FadingAnimatedIconFrameRenderer.FadeType.FadeIn))
|
||||
}
|
||||
if (frame.duration > 0L) {
|
||||
renderers.add(base)
|
||||
}
|
||||
if (frame.fadeOut > 0L) {
|
||||
renderers.add(FadingAnimatedIconFrameRenderer(base, frame.fadeOut, FadingAnimatedIconFrameRenderer.FadeType.FadeOut))
|
||||
}
|
||||
}
|
||||
return renderers
|
||||
}
|
||||
|
||||
override fun render(paintingContext: LayerPaintingContext) {
|
||||
val placement = layout.placement(paintingContext)
|
||||
val nestedLayerApi =
|
||||
paintingContext.createNestedLayer(
|
||||
placement.bounds.x,
|
||||
placement.bounds.y,
|
||||
placement.bounds.width,
|
||||
placement.bounds.height,
|
||||
scale = placement.scale,
|
||||
)
|
||||
val currentFrameData = calculateAndSetNewFrameData()
|
||||
frameRenderers[currentFrameData.frame].render(nestedLayerApi)
|
||||
if (currentFrameData.remainingDuration > 0L) {
|
||||
renderingContext.updateFlow.triggerDelayedUpdate(currentFrameData.remainingDuration)
|
||||
}
|
||||
private fun applyLayout(): LayerLayout {
|
||||
val dimensions = frameRenderers.maxCompoundSize { it.layout.consumedSpace() }
|
||||
return applyLayout(layer.modifier, dimensions.width, dimensions.height)
|
||||
}
|
||||
|
||||
override fun render(paintingContext: LayerPaintingContext) {
|
||||
val placement = layout.placement(paintingContext)
|
||||
val nestedLayerApi =
|
||||
paintingContext.createNestedLayer(
|
||||
placement.bounds.x,
|
||||
placement.bounds.y,
|
||||
placement.bounds.width,
|
||||
placement.bounds.height,
|
||||
scale = placement.scale,
|
||||
)
|
||||
val currentFrameData = calculateAndSetNewFrameData()
|
||||
val elapsedTime = System.currentTimeMillis() - currentFrameData.timestamp
|
||||
frameRenderers[currentFrameData.frame].render(nestedLayerApi, elapsedTime)
|
||||
if (currentFrameData.refreshDuration > 0L) {
|
||||
renderingContext.updateFlow.triggerDelayedUpdate(currentFrameData.refreshDuration)
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateAndSetNewFrameData(): FrameData {
|
||||
val currentLastFrame = lastFrame
|
||||
if (currentLastFrame.timestamp == -1L) {
|
||||
val remaining = if (frameRenderers.size > 0) frameRenderers[0].frame.duration else 0L
|
||||
val newData = FrameData(System.currentTimeMillis(), 0, remaining)
|
||||
lastFrame = newData
|
||||
return newData
|
||||
}
|
||||
val elapsedMillis = System.currentTimeMillis() - lastFrame.timestamp
|
||||
if (elapsedMillis > currentLastFrame.remainingDuration) {
|
||||
val index = (currentLastFrame.frame + 1) % frameRenderers.size
|
||||
val remaining = frameRenderers[index].frame.duration
|
||||
val newData = FrameData(System.currentTimeMillis(), index, remaining)
|
||||
lastFrame = newData
|
||||
return newData
|
||||
} else {
|
||||
return lastFrame
|
||||
}
|
||||
private fun calculateAndSetNewFrameData(): FrameData {
|
||||
val currentLastFrame = lastFrame
|
||||
// Handle first frame
|
||||
if (currentLastFrame.timestamp == -1L) {
|
||||
val remaining = if (frameRenderers.isNotEmpty()) frameRenderers[0].getDuration() else 0L
|
||||
val refresh = if (frameRenderers.isNotEmpty()) frameRenderers[0].getRefreshDuration() else 0L
|
||||
val newData = FrameData(System.currentTimeMillis(), 0, remaining, refresh)
|
||||
lastFrame = newData
|
||||
return newData
|
||||
}
|
||||
|
||||
private class FrameData(val timestamp: Long, val frame: Int, val remainingDuration: Long)
|
||||
|
||||
private class AnimatedIconFrameRenderer(val frame: IconAnimationFrame, renderingContext: RenderingContext) {
|
||||
private val renderers = IconLayerManager.createRenderers(frame.layers, renderingContext)
|
||||
|
||||
val layout: LayerLayout = applyLayout()
|
||||
|
||||
private fun applyLayout(): LayerLayout {
|
||||
val dimensions = renderers.maxCompoundSize { it.layout.consumedSpace() }
|
||||
return LayerLayout(
|
||||
0.dp.compoundSize(),
|
||||
0.dp.compoundSize(),
|
||||
0.dp.compoundSize(),
|
||||
0.dp.compoundSize(),
|
||||
dimensions.width,
|
||||
dimensions.height,
|
||||
)
|
||||
}
|
||||
|
||||
fun render(paintingContext: LayerPaintingContext) {
|
||||
for (layer in renderers) {
|
||||
layer.render(paintingContext)
|
||||
}
|
||||
}
|
||||
// Handle next frame
|
||||
val elapsedMillis = System.currentTimeMillis() - lastFrame.timestamp
|
||||
if (elapsedMillis > currentLastFrame.remainingDuration) {
|
||||
val index = (currentLastFrame.frame + 1) % frameRenderers.size
|
||||
val remaining = frameRenderers[index].getDuration()
|
||||
val refresh = frameRenderers[index].getRefreshDuration()
|
||||
val newData = FrameData(System.currentTimeMillis(), index, remaining, refresh)
|
||||
lastFrame = newData
|
||||
return newData
|
||||
}
|
||||
else {
|
||||
return lastFrame
|
||||
}
|
||||
}
|
||||
|
||||
private class FrameData(val timestamp: Long, val frame: Int, val remainingDuration: Long, val refreshDuration: Long)
|
||||
}
|
||||
|
||||
interface AnimatedIconFrameRenderer {
|
||||
fun getDuration(): Long
|
||||
fun getRefreshDuration(): Long
|
||||
val layout: LayerLayout
|
||||
fun render(paintingContext: LayerPaintingContext, elapsedMillis: Long)
|
||||
}
|
||||
|
||||
private class FadingAnimatedIconFrameRenderer(val base: BaseAnimatedIconFrameRenderer, private val duration: Long, val fadeType: FadeType) : AnimatedIconFrameRenderer {
|
||||
override val layout: LayerLayout = base.layout
|
||||
|
||||
override fun getDuration(): Long {
|
||||
return duration
|
||||
}
|
||||
|
||||
override fun getRefreshDuration(): Long {
|
||||
return 50 // refresh rate for fading animation
|
||||
}
|
||||
|
||||
override fun render(paintingContext: LayerPaintingContext, elapsedMillis: Long) {
|
||||
val alpha = when (fadeType) {
|
||||
FadeType.FadeIn -> fadeInAlpha(elapsedMillis, duration)
|
||||
FadeType.FadeOut -> fadeOutAlpha(elapsedMillis, duration)
|
||||
}
|
||||
val nested = paintingContext.createNestedLayer(alpha = alpha)
|
||||
base.render(nested, elapsedMillis)
|
||||
}
|
||||
|
||||
private fun fadeInAlpha(elapsedMillis: Long, durationMillis: Long): Float {
|
||||
if (durationMillis <= 0) return 1f
|
||||
|
||||
val progress = (elapsedMillis.toDouble() / durationMillis).coerceIn(0.0, 1.0)
|
||||
return ((1 - cos(Math.PI * progress)) / 2).toFloat()
|
||||
}
|
||||
|
||||
private fun fadeOutAlpha(elapsedMillis: Long, durationMillis: Long): Float {
|
||||
if (durationMillis <= 0) return 0f
|
||||
|
||||
val progress = (elapsedMillis.toDouble() / durationMillis).coerceIn(0.0, 1.0)
|
||||
return ((cos(Math.PI * progress) + 1) / 2).toFloat()
|
||||
}
|
||||
|
||||
enum class FadeType { FadeIn, FadeOut }
|
||||
}
|
||||
|
||||
private class BaseAnimatedIconFrameRenderer(val frame: IconAnimationFrame, renderingContext: RenderingContext) : AnimatedIconFrameRenderer {
|
||||
private val renderers = IconLayerManager.createRenderers(frame.layers, renderingContext)
|
||||
|
||||
override fun getDuration(): Long {
|
||||
return frame.duration
|
||||
}
|
||||
|
||||
override fun getRefreshDuration(): Long {
|
||||
return frame.duration
|
||||
}
|
||||
|
||||
override val layout: LayerLayout = applyLayout()
|
||||
|
||||
private fun applyLayout(): LayerLayout {
|
||||
val dimensions = renderers.maxCompoundSize { it.layout.consumedSpace() }
|
||||
return LayerLayout(
|
||||
0.dp.compoundSize(),
|
||||
0.dp.compoundSize(),
|
||||
0.dp.compoundSize(),
|
||||
0.dp.compoundSize(),
|
||||
dimensions.width,
|
||||
dimensions.height,
|
||||
)
|
||||
}
|
||||
|
||||
override fun render(paintingContext: LayerPaintingContext, elapsedMillis: Long) {
|
||||
for (layer in renderers) {
|
||||
layer.render(paintingContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,11 @@ import com.intellij.icons.AllIcons;
|
||||
import com.intellij.openapi.application.CoroutineSupport.UiDispatcherKind;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.platform.icons.modifiers.IconModifier;
|
||||
import com.intellij.ui.icons.IconUtilKt;
|
||||
import com.intellij.util.concurrency.EdtScheduler;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import kotlin.Unit;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -26,6 +28,9 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static com.intellij.platform.icons.IconManagerKt.icon;
|
||||
import static com.intellij.platform.icons.swing.SwingIconKt.swingIcon;
|
||||
|
||||
public class AnimatedIcon implements Icon {
|
||||
private static final Logger LOG = Logger.getInstance(AnimatedIcon.class);
|
||||
/**
|
||||
@@ -172,11 +177,16 @@ public class AnimatedIcon implements Icon {
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static final class Fading extends AnimatedIcon {
|
||||
private final Icon icon;
|
||||
private final int period;
|
||||
|
||||
public Fading(@NotNull Icon icon) {
|
||||
this(1000, icon);
|
||||
}
|
||||
|
||||
public Fading(int period, @NotNull Icon icon) {
|
||||
this.period = period;
|
||||
this.icon = icon;
|
||||
super(50, new Icon() {
|
||||
private final long time = System.currentTimeMillis();
|
||||
|
||||
@@ -213,9 +223,30 @@ public class AnimatedIcon implements Icon {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
@Override
|
||||
public com.intellij.platform.icons.Icon extractAsNewIcon() {
|
||||
final int fadeTime = Math.max(period / 2, 0);
|
||||
return icon((designer) -> {
|
||||
designer.animation(IconModifier.Companion, (animation) -> {
|
||||
animation.frame(fadeTime, 0, fadeTime, (frameDesigner) -> {
|
||||
swingIcon(frameDesigner, icon, IconModifier.Companion);
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
animation.frame(fadeTime, 0, fadeTime, (frameDesigner) -> {
|
||||
swingIcon(frameDesigner, icon, IconModifier.Companion);
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
final Frame[] frames;
|
||||
@ApiStatus.Internal
|
||||
public final Frame[] frames;
|
||||
private final Set<Component> requested = Collections.newSetFromMap(new IdentityHashMap<>());
|
||||
private long time;
|
||||
private int index;
|
||||
@@ -341,4 +372,20 @@ public class AnimatedIcon implements Icon {
|
||||
protected @Nullable Component getRendererOwner(@Nullable Component component) {
|
||||
return ClientProperty.isTrue(component, ANIMATION_IN_RENDERER_ALLOWED) ? component : null;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public com.intellij.platform.icons.Icon extractAsNewIcon() {
|
||||
return icon((designer) -> {
|
||||
designer.animation(IconModifier.Companion, (animation) -> {
|
||||
for (Frame frame : frames) {
|
||||
animation.frame(frame.getDelay(), (frameDesigner) -> {
|
||||
swingIcon(frameDesigner, frame.getIcon(), IconModifier.Companion);
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+7
-4
@@ -34,6 +34,7 @@ public class ComposeLayerPaintingContext(
|
||||
override val slotWidth: Int? = null,
|
||||
override val slotHeight: Int? = null,
|
||||
private val overrideColorFilter: ColorFilter? = null,
|
||||
override val alpha: Float = 1f,
|
||||
override val scaling: ScalingContext = DefaultScalingContext(drawScope.drawContext.density.density, 1f),
|
||||
) : LayerPaintingContext {
|
||||
override fun createNestedLayer(
|
||||
@@ -42,6 +43,7 @@ public class ComposeLayerPaintingContext(
|
||||
slotWidth: Int?,
|
||||
slotHeight: Int?,
|
||||
scale: Float,
|
||||
alpha: Float,
|
||||
overrideColorFilter: ColorFilter?,
|
||||
): LayerPaintingContext {
|
||||
return ComposeLayerPaintingContext(
|
||||
@@ -51,6 +53,7 @@ public class ComposeLayerPaintingContext(
|
||||
slotWidth,
|
||||
slotHeight,
|
||||
overrideColorFilter ?: this.overrideColorFilter,
|
||||
alpha,
|
||||
DefaultScalingContext(scaling.displayDensity, scaling.contextScale * scale),
|
||||
)
|
||||
}
|
||||
@@ -68,7 +71,7 @@ public class ComposeLayerPaintingContext(
|
||||
color.toCompose(),
|
||||
radius,
|
||||
Offset(x.toFloat(), y.toFloat()),
|
||||
alpha,
|
||||
alpha * this.alpha,
|
||||
style = style,
|
||||
blendMode = blendMode,
|
||||
)
|
||||
@@ -87,7 +90,7 @@ public class ComposeLayerPaintingContext(
|
||||
color.toCompose(),
|
||||
Offset(x.toFloat(), y.toFloat()),
|
||||
Size(width.toFloat(), height.toFloat()),
|
||||
alpha,
|
||||
alpha * this.alpha,
|
||||
style = style,
|
||||
blendMode = blendMode,
|
||||
)
|
||||
@@ -189,7 +192,7 @@ public class ComposeLayerPaintingContext(
|
||||
with(image.painter) {
|
||||
draw(
|
||||
Size(targetSize.width.toFloat(), targetSize.height.toFloat()),
|
||||
alpha,
|
||||
alpha * this@ComposeLayerPaintingContext.alpha,
|
||||
convertColorFilter(colorFilter ?: image.modifiers?.colorFilter),
|
||||
)
|
||||
}
|
||||
@@ -219,7 +222,7 @@ public class ComposeLayerPaintingContext(
|
||||
IntSize(srcWidth ?: image.size.width, srcHeight ?: image.size.height),
|
||||
dstOffset = IntOffset(x, y),
|
||||
dstSize = targetSize,
|
||||
alpha = alpha,
|
||||
alpha = alpha * this.alpha,
|
||||
colorFilter = convertColorFilter(colorFilter),
|
||||
filterQuality = FilterQuality.Low,
|
||||
)
|
||||
|
||||
+58
@@ -22,9 +22,14 @@ import com.intellij.platform.icons.scale.factor
|
||||
import com.intellij.platform.icons.scale.fitArea
|
||||
import com.intellij.platform.icons.swing.swingIcon
|
||||
import com.intellij.platform.icons.swing.toNewIcon
|
||||
import com.intellij.ui.AnimatedIcon
|
||||
import com.intellij.ui.IconDeferrer
|
||||
import com.intellij.ui.dsl.builder.panel
|
||||
import kotlinx.coroutines.delay
|
||||
import javax.swing.Icon
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JLabel
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
internal class IconsPanel : UISandboxPanel {
|
||||
@@ -64,6 +69,22 @@ internal class IconsPanel : UISandboxPanel {
|
||||
}
|
||||
}
|
||||
}
|
||||
icon {
|
||||
animation {
|
||||
frame(fadeIn = 500, stay = 500, fadeOut = 500) {
|
||||
swingIcon(AllIcons.General.Add)
|
||||
}
|
||||
frame(fadeIn = 500, stay = 500, fadeOut = 500) {
|
||||
swingIcon(AllIcons.General.Remove)
|
||||
}
|
||||
frame(fadeIn = 500, stay = 500, fadeOut = 500) {
|
||||
swingIcon(AllIcons.General.ArrowUp)
|
||||
}
|
||||
frame(fadeIn = 500, stay = 500, fadeOut = 500) {
|
||||
swingIcon(AllIcons.General.ArrowDown)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
group("Deferred Icon") {
|
||||
@@ -120,6 +141,43 @@ internal class IconsPanel : UISandboxPanel {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var i = 0
|
||||
fun createDeferredIcon(): Icon {
|
||||
return IconDeferrer.getInstance().deferAsync(AllIcons.FileTypes.Unknown, i++) {
|
||||
delay(5000.milliseconds)
|
||||
AllIcons.FileTypes.Bazel
|
||||
}
|
||||
}
|
||||
group("Legacy Icon Support") {
|
||||
lateinit var deferredIconLabel: JLabel
|
||||
row {
|
||||
icon(
|
||||
AllIcons.General.GearPlain.toNewIcon()
|
||||
)
|
||||
icon(
|
||||
AnimatedIcon.Default().toNewIcon()
|
||||
)
|
||||
deferredIconLabel = icon(
|
||||
createDeferredIcon()
|
||||
).component
|
||||
}
|
||||
row {
|
||||
button("Recreate Deferred Icon") {
|
||||
deferredIconLabel.icon = createDeferredIcon()
|
||||
}
|
||||
}
|
||||
}
|
||||
group("Legacy Icon Tests") {
|
||||
row {
|
||||
icon(
|
||||
AnimatedIcon.Blinking(AllIcons.General.Remove)
|
||||
)
|
||||
icon(
|
||||
AnimatedIcon.Fading(AllIcons.General.Add)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user