From bdf6b14b0b254dbc1dc5ffb229e244bf2c41f859 Mon Sep 17 00:00:00 2001 From: Jakub Senohrabek Date: Thu, 5 Feb 2026 13:42:31 +0100 Subject: [PATCH 1/9] IJPL-176416 New Icons API GitOrigin-RevId: 820ec33e12e0e36356344d66c7a7b4720f70e31c --- .idea/modules.xml | 7 + build/bazel-generated-file-list.txt | 6 + lib/BUILD.bazel | 22 ++ lib/MODULE.bazel | 14 + .../build/productLayout/CoreModuleSets.kt | 8 + platform/core-ui/BUILD.bazel | 1 + .../core-ui/intellij.platform.core.ui.iml | 1 + .../resources/intellij.platform.core.ui.xml | 1 + platform/icons-api/.gitignore | 111 ++++++++ platform/icons-api/BUILD.bazel | 36 +++ platform/icons-api/README.md | 72 +++++ platform/icons-api/STATE.md | 53 ++++ platform/icons-api/build.gradle.kts | 24 ++ .../icons-api/intellij.platform.icons.api.iml | 37 +++ .../icons-api/legacy-icon-support/BUILD.bazel | 33 +++ .../icons-api/legacy-icon-support/README.md | 15 ++ ...j.platform.icons.api.legacyIconSupport.iml | 38 +++ ...j.platform.icons.api.legacyIconSupport.xml | 9 + .../icons/legacyIconSupport/SwingIcon.kt | 42 +++ .../icons/legacyIconSupport/SwingIconLayer.kt | 15 ++ .../legacyIconSupport/SwingIconManager.kt | 10 + platform/icons-api/rendering/BUILD.bazel | 36 +++ platform/icons-api/rendering/build.gradle.kts | 24 ++ .../intellij.platform.icons.api.rendering.iml | 31 +++ .../icons-api/rendering/lowlevel/BUILD.bazel | 31 +++ .../rendering/lowlevel/build.gradle.kts | 24 ++ ....platform.icons.api.rendering.lowlevel.iml | 31 +++ ....platform.icons.api.rendering.lowlevel.xml | 8 + .../lowlevel/GPUImageResourceHolder.kt | 10 + .../intellij.platform.icons.api.rendering.xml | 6 + .../icons/rendering/BitmapImageResource.kt | 24 ++ .../org/jetbrains/icons/rendering/Bounds.kt | 85 ++++++ .../jetbrains/icons/rendering/IconRenderer.kt | 15 ++ .../icons/rendering/IconRendererManager.kt | 59 +++++ .../icons/rendering/IconUpdateFlow.kt | 16 ++ .../icons/rendering/ImageResource.kt | 31 +++ .../icons/rendering/ImageResourceLoader.kt | 7 + .../icons/rendering/ImageResourceProvider.kt | 26 ++ .../icons/rendering/LoadingOptions.kt | 17 ++ .../jetbrains/icons/rendering/PaintingApi.kt | 47 ++++ .../icons/rendering/RenderingContext.kt | 30 +++ .../rendering/RescalableImageResource.kt | 64 +++++ .../resources/intellij.platform.icons.api.xml | 8 + .../src/org/jetbrains/icons/DynamicIcon.kt | 11 + .../jetbrains/icons/ExperimentalIconsApi.kt | 26 ++ .../icons-api/src/org/jetbrains/icons/Icon.kt | 18 ++ .../src/org/jetbrains/icons/IconManager.kt | 68 +++++ .../org/jetbrains/icons/InternalIconsApi.kt | 23 ++ .../org/jetbrains/icons/design/BlendMode.kt | 16 ++ .../src/org/jetbrains/icons/design/Color.kt | 82 ++++++ .../org/jetbrains/icons/design/IconAlign.kt | 61 +++++ .../jetbrains/icons/design/IconDesigner.kt | 46 ++++ .../org/jetbrains/icons/design/IconMargin.kt | 44 +++ .../org/jetbrains/icons/design/IconUnits.kt | 153 +++++++++++ .../src/org/jetbrains/icons/design/Shape.kt | 27 ++ .../icons/design/SvgPatcherDesigner.kt | 56 ++++ .../jetbrains/icons/filters/ColorFilter.kt | 9 + .../icons/filters/TintColorFilter.kt | 15 ++ .../org/jetbrains/icons/layers/IconLayer.kt | 13 + .../icons/modifiers/AlignIconModifier.kt | 33 +++ .../icons/modifiers/AlphaIconModifier.kt | 32 +++ .../icons/modifiers/ColorFilterModifier.kt | 41 +++ .../icons/modifiers/CombinedIconModifier.kt | 35 +++ .../icons/modifiers/CutoutMarginModifier.kt | 38 +++ .../icons/modifiers/HeightIconModifier.kt | 42 +++ .../jetbrains/icons/modifiers/IconModifier.kt | 37 +++ .../icons/modifiers/MarginIconModifier.kt | 56 ++++ .../icons/modifiers/StrokeModifier.kt | 35 +++ .../icons/modifiers/SvgPatcherModifier.kt | 44 +++ .../icons/modifiers/WidthIconModifier.kt | 34 +++ .../jetbrains/icons/patchers/SvgPatcher.kt | 123 +++++++++ platform/icons-impl/.gitignore | 111 ++++++++ platform/icons-impl/BUILD.bazel | 46 ++++ platform/icons-impl/README.md | 3 + platform/icons-impl/build.gradle.kts | 27 ++ .../intellij.platform.icons.impl.iml | 42 +++ platform/icons-impl/intellij/BUILD.bazel | 90 +++++++ .../intellij.platform.icons.impl.intellij.iml | 75 ++++++ .../intellij.platform.icons.impl.intellij.xml | 32 +++ .../impl/intellij/IntelliJIconManager.kt | 54 ++++ .../custom/CustomIconLayerRegistration.kt | 16 ++ .../custom/CustomIconLayerRendererProvider.kt | 25 ++ .../custom/CustomImageResourceLoader.kt | 17 ++ .../custom/CustomLegacyIconSerializer.kt | 16 ++ .../custom/CustomSerializableRegistration.kt | 41 +++ .../intellij/design/IntelliJIconDesigner.kt | 19 ++ .../impl/intellij/rendering/Convertors.kt | 108 ++++++++ .../intellij/rendering/IconUpdateService.kt | 30 +++ .../rendering/IntelliJIconRendererManager.kt | 52 ++++ .../rendering/IntelliJImageResourceLoader.kt | 85 ++++++ .../IntelliJImageResourceProvider.kt | 89 +++++++ .../IntelliJMutableIconUpdateFlowImpl.kt | 24 ++ .../rendering/SwingIconLayerRegistration.kt | 12 + .../rendering/SwingIconLayerRenderer.kt | 73 +++++ .../serializers/CachedImageIconSerializer.kt | 35 +++ .../intellij/DefaultIconSerializationTest.kt | 34 +++ .../icons/impl/intellij/DynamicIconTest.kt | 58 ++++ .../intellij.platform.icons.impl.xml | 12 + .../icons/impl/DefaultDynamicIcon.kt | 57 ++++ .../icons/impl/DefaultIconManager.kt | 91 +++++++ .../icons/impl/DefaultLayeredIcon.kt | 29 ++ .../icons/impl/SerializersBuilder.kt | 14 + .../design/DefaultIconAnimationDesigner.kt | 22 ++ .../icons/impl/design/DefaultIconDesigner.kt | 66 +++++ .../icons/impl/layers/AnimatedIconLayer.kt | 35 +++ .../icons/impl/layers/IconIconLayer.kt | 35 +++ .../icons/impl/layers/IconLayerConstraints.kt | 44 +++ .../icons/impl/layers/IconLayerManager.kt | 30 +++ .../icons/impl/layers/ImageIconLayer.kt | 35 +++ .../icons/impl/layers/LayoutIconLayer.kt | 44 +++ .../icons/impl/layers/ModifierHelpers.kt | 29 ++ .../impl/layers/NaivePolymorphicSerializer.kt | 77 ++++++ .../icons/impl/layers/ShapeIconLayer.kt | 40 +++ .../icons/impl/rendering/AwtImageResource.kt | 84 ++++++ .../rendering/CachedGPUImageResourceHolder.kt | 18 ++ .../CoroutineBasedMutableIconUpdateFlow.kt | 29 ++ .../rendering/DefaultDynamicIconRenderer.kt | 47 ++++ .../impl/rendering/DefaultIconRenderer.kt | 51 ++++ .../rendering/DefaultIconRendererManager.kt | 66 +++++ .../impl/rendering/DefaultImageModifiers.kt | 42 +++ .../rendering/DefaultImageResourceProvider.kt | 10 + .../impl/rendering/IconAnimationFrame.kt | 33 +++ .../rendering/MutableIconUpdateFlowBase.kt | 39 +++ .../icons/impl/rendering/SwingIcon.kt | 63 +++++ .../icons/impl/rendering/SwingPaintingApi.kt | 212 +++++++++++++++ .../layers/AnimatedIconLayerRenderer.kt | 89 +++++++ .../rendering/layers/DefaultLayerLayout.kt | 83 ++++++ .../rendering/layers/IconIconLayerRenderer.kt | 37 +++ .../rendering/layers/IconLayerRenderer.kt | 12 + .../layers/ImageIconLayerRenderer.kt | 45 ++++ .../impl/rendering/layers/LayerHelpers.kt | 41 +++ .../layers/LayoutIconLayerRenderer.kt | 80 ++++++ .../layers/ShapeIconLayerRenderer.kt | 70 +++++ .../modifiers/ColorFilterModifier.kt | 9 + .../impl/rendering/modifiers/IconModifier.kt | 121 +++++++++ .../rendering/modifiers/MarginIconModifier.kt | 25 ++ .../icons/impl/rendering/toAwtColor.kt | 12 + .../intellij.moduleSets.core.platform.xml | 4 + platform/jewel/build.gradle.kts | 2 + platform/jewel/gradle/libs.versions.toml | 4 +- platform/jewel/ide-laf-bridge/BUILD.bazel | 4 +- .../intellij.platform.jewel.ideLafBridge.iml | 3 +- .../jewel/bridge/JewelComposePanelWrapper.kt | 2 +- .../int-ui/int-ui-standalone/BUILD.bazel | 6 +- .../int-ui/int-ui-standalone/build.gradle.kts | 3 + ...tellij.platform.jewel.intUi.standalone.iml | 3 +- .../standalone/icon/StandaloneIconDesigner.kt | 14 + .../standalone/icon/StandaloneIconManager.kt | 14 + .../icon/StandaloneIconRendererManager.kt | 50 ++++ .../org.jetbrains.icons.api.IconManager | 1 + ...ns.icons.api.rendering.IconRendererManager | 1 + ....icons.api.rendering.ImageResourceProvider | 1 + platform/jewel/samples/showcase/BUILD.bazel | 1 + platform/jewel/samples/showcase/api-dump.txt | 10 +- .../showcase/exposed-third-party-api.txt | 1 + ...tellij.platform.jewel.samples.showcase.iml | 2 +- .../jewel/samples/showcase/ShowcaseIcons.kt | 28 +- .../samples/showcase/components/Icons.kt | 69 ++++- .../samples/standalone/view/TitleBarView.kt | 10 +- .../samples/standalone/view/WelcomeView.kt | 4 +- platform/jewel/settings.gradle.kts | 10 + platform/jewel/ui/BUILD.bazel | 16 +- platform/jewel/ui/api-dump-experimental.txt | 8 + platform/jewel/ui/api-dump.txt | 6 + platform/jewel/ui/build.gradle.kts | 4 + platform/jewel/ui/exposed-third-party-api.txt | 1 + .../jewel/ui/intellij.platform.jewel.ui.iml | 6 +- .../org/jetbrains/jewel/ui/component/Icon.kt | 64 ++++- .../ui/icon/ComposeBitmapImageResource.kt | 130 +++++++++ .../ui/icon/ComposeImageResourceProvider.kt | 105 ++++++++ .../ui/icon/ComposePainterImageResource.kt | 15 ++ .../jewel/ui/icon/ComposePaintingApi.kt | 231 ++++++++++++++++ .../org/jetbrains/jewel/ui/icon/IconKey.kt | 8 + .../org/jetbrains/jewel/ui/icon/IconUtils.kt | 103 ++++++++ .../jewel/ui/icon/RendererBasedIconPainter.kt | 59 +++++ .../jewel/ui/icon/ResourceImageIconLoader.kt | 15 ++ platform/platform-impl/bootstrap/BUILD.bazel | 2 + .../intellij.platform.ide.bootstrap.iml | 2 + .../com/intellij/platform/ide/bootstrap/ui.kt | 13 +- .../META-INF/intellij.moduleSets.core.ide.xml | 4 + .../intellij.moduleSets.core.lang.xml | 6 + .../intellij.moduleSets.essential.minimal.xml | 6 + .../intellij.moduleSets.essential.xml | 6 + .../intellij.moduleSets.ide.common.xml | 6 + platform/util/BUILD.bazel | 6 + platform/util/intellij.platform.util.iml | 3 + .../com/intellij/ui/icons/CachedImageIcon.kt | 3 +- .../ui/icons/ImageDataByPathLoader.kt | 33 ++- .../src/com/intellij/ui/icons/LegacyIcon.kt | 35 +++ .../intellij.devkit.compose/BUILD.bazel | 6 + .../intellij.devkit.compose.iml | 7 +- .../src/demo/ComponentShowcaseTab.kt | 22 +- .../showcase/ComposePerformanceDemoAction.kt | 128 ++++++++- .../src/showcase/ComposeShowcase.kt | 46 +--- .../src/showcase/ComposeShowcaseAction.kt | 8 +- .../src/showcase/Icons.kt | 250 ++++++++++++++++++ 196 files changed, 7123 insertions(+), 82 deletions(-) create mode 100644 platform/icons-api/.gitignore create mode 100644 platform/icons-api/BUILD.bazel create mode 100644 platform/icons-api/README.md create mode 100644 platform/icons-api/STATE.md create mode 100644 platform/icons-api/build.gradle.kts create mode 100644 platform/icons-api/intellij.platform.icons.api.iml create mode 100644 platform/icons-api/legacy-icon-support/BUILD.bazel create mode 100644 platform/icons-api/legacy-icon-support/README.md create mode 100644 platform/icons-api/legacy-icon-support/intellij.platform.icons.api.legacyIconSupport.iml create mode 100644 platform/icons-api/legacy-icon-support/resources/intellij.platform.icons.api.legacyIconSupport.xml create mode 100644 platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIcon.kt create mode 100644 platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIconLayer.kt create mode 100644 platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIconManager.kt create mode 100644 platform/icons-api/rendering/BUILD.bazel create mode 100644 platform/icons-api/rendering/build.gradle.kts create mode 100644 platform/icons-api/rendering/intellij.platform.icons.api.rendering.iml create mode 100644 platform/icons-api/rendering/lowlevel/BUILD.bazel create mode 100644 platform/icons-api/rendering/lowlevel/build.gradle.kts create mode 100644 platform/icons-api/rendering/lowlevel/intellij.platform.icons.api.rendering.lowlevel.iml create mode 100644 platform/icons-api/rendering/lowlevel/resources/intellij.platform.icons.api.rendering.lowlevel.xml create mode 100644 platform/icons-api/rendering/lowlevel/src/org/jetbrains/icons/rendering/lowlevel/GPUImageResourceHolder.kt create mode 100644 platform/icons-api/rendering/resources/intellij.platform.icons.api.rendering.xml create mode 100644 platform/icons-api/rendering/src/org/jetbrains/icons/rendering/BitmapImageResource.kt create mode 100644 platform/icons-api/rendering/src/org/jetbrains/icons/rendering/Bounds.kt create mode 100644 platform/icons-api/rendering/src/org/jetbrains/icons/rendering/IconRenderer.kt create mode 100644 platform/icons-api/rendering/src/org/jetbrains/icons/rendering/IconRendererManager.kt create mode 100644 platform/icons-api/rendering/src/org/jetbrains/icons/rendering/IconUpdateFlow.kt create mode 100644 platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResource.kt create mode 100644 platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResourceLoader.kt create mode 100644 platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResourceProvider.kt create mode 100644 platform/icons-api/rendering/src/org/jetbrains/icons/rendering/LoadingOptions.kt create mode 100644 platform/icons-api/rendering/src/org/jetbrains/icons/rendering/PaintingApi.kt create mode 100644 platform/icons-api/rendering/src/org/jetbrains/icons/rendering/RenderingContext.kt create mode 100644 platform/icons-api/rendering/src/org/jetbrains/icons/rendering/RescalableImageResource.kt create mode 100644 platform/icons-api/resources/intellij.platform.icons.api.xml create mode 100644 platform/icons-api/src/org/jetbrains/icons/DynamicIcon.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/ExperimentalIconsApi.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/Icon.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/IconManager.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/InternalIconsApi.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/design/BlendMode.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/design/Color.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/design/IconAlign.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/design/IconDesigner.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/design/IconMargin.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/design/IconUnits.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/design/Shape.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/design/SvgPatcherDesigner.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/filters/ColorFilter.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/filters/TintColorFilter.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/layers/IconLayer.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/modifiers/AlignIconModifier.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/modifiers/AlphaIconModifier.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/modifiers/ColorFilterModifier.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/modifiers/CombinedIconModifier.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/modifiers/CutoutMarginModifier.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/modifiers/HeightIconModifier.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/modifiers/IconModifier.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/modifiers/MarginIconModifier.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/modifiers/StrokeModifier.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/modifiers/SvgPatcherModifier.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/modifiers/WidthIconModifier.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/patchers/SvgPatcher.kt create mode 100644 platform/icons-impl/.gitignore create mode 100644 platform/icons-impl/BUILD.bazel create mode 100644 platform/icons-impl/README.md create mode 100644 platform/icons-impl/build.gradle.kts create mode 100644 platform/icons-impl/intellij.platform.icons.impl.iml create mode 100644 platform/icons-impl/intellij/BUILD.bazel create mode 100644 platform/icons-impl/intellij/intellij.platform.icons.impl.intellij.iml create mode 100644 platform/icons-impl/intellij/resources/intellij.platform.icons.impl.intellij.xml create mode 100644 platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt create mode 100644 platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomIconLayerRegistration.kt create mode 100644 platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomIconLayerRendererProvider.kt create mode 100644 platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomImageResourceLoader.kt create mode 100644 platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomLegacyIconSerializer.kt create mode 100644 platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomSerializableRegistration.kt create mode 100644 platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/design/IntelliJIconDesigner.kt create mode 100644 platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/Convertors.kt create mode 100644 platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IconUpdateService.kt create mode 100644 platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJIconRendererManager.kt create mode 100644 platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJImageResourceLoader.kt create mode 100644 platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJImageResourceProvider.kt create mode 100644 platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJMutableIconUpdateFlowImpl.kt create mode 100644 platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRegistration.kt create mode 100644 platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRenderer.kt create mode 100644 platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/serializers/CachedImageIconSerializer.kt create mode 100644 platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DefaultIconSerializationTest.kt create mode 100644 platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DynamicIconTest.kt create mode 100644 platform/icons-impl/resources/intellij.platform.icons.impl.xml create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/DefaultDynamicIcon.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/DefaultIconManager.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/DefaultLayeredIcon.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/SerializersBuilder.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/design/DefaultIconAnimationDesigner.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/design/DefaultIconDesigner.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/layers/AnimatedIconLayer.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/layers/IconIconLayer.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/layers/IconLayerConstraints.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/layers/IconLayerManager.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/layers/ImageIconLayer.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/layers/LayoutIconLayer.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/layers/ModifierHelpers.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/layers/NaivePolymorphicSerializer.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/layers/ShapeIconLayer.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/rendering/AwtImageResource.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/rendering/CachedGPUImageResourceHolder.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/rendering/CoroutineBasedMutableIconUpdateFlow.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultDynamicIconRenderer.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultIconRenderer.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultIconRendererManager.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultImageModifiers.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultImageResourceProvider.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/rendering/IconAnimationFrame.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/rendering/MutableIconUpdateFlowBase.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingIcon.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingPaintingApi.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/AnimatedIconLayerRenderer.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/DefaultLayerLayout.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/IconIconLayerRenderer.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/IconLayerRenderer.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/ImageIconLayerRenderer.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/LayerHelpers.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/LayoutIconLayerRenderer.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/ShapeIconLayerRenderer.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/rendering/modifiers/ColorFilterModifier.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/rendering/modifiers/IconModifier.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/rendering/modifiers/MarginIconModifier.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/rendering/toAwtColor.kt create mode 100644 platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconDesigner.kt create mode 100644 platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconManager.kt create mode 100644 platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconRendererManager.kt create mode 100644 platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.api.IconManager create mode 100644 platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.api.rendering.IconRendererManager create mode 100644 platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.api.rendering.ImageResourceProvider create mode 100644 platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeBitmapImageResource.kt create mode 100644 platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeImageResourceProvider.kt create mode 100644 platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposePainterImageResource.kt create mode 100644 platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposePaintingApi.kt create mode 100644 platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/IconUtils.kt create mode 100644 platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/RendererBasedIconPainter.kt create mode 100644 platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ResourceImageIconLoader.kt create mode 100644 platform/util/ui/src/com/intellij/ui/icons/LegacyIcon.kt create mode 100644 plugins/devkit/intellij.devkit.compose/src/showcase/Icons.kt diff --git a/.idea/modules.xml b/.idea/modules.xml index fde07b719fc3e..d6b6f810443bc 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -1191,6 +1191,12 @@ + + + + + + @@ -1239,6 +1245,7 @@ + diff --git a/build/bazel-generated-file-list.txt b/build/bazel-generated-file-list.txt index 319b6d8bd64e2..c06cbe7e2e0ee 100644 --- a/build/bazel-generated-file-list.txt +++ b/build/bazel-generated-file-list.txt @@ -620,6 +620,12 @@ platform/find/backend platform/foldings platform/forms_rt platform/icons +platform/icons-api +platform/icons-api/legacy-icon-support +platform/icons-api/rendering +platform/icons-api/rendering/lowlevel +platform/icons-impl +platform/icons-impl/intellij platform/ide-core platform/ide-core-impl platform/ide-core/plugins diff --git a/lib/BUILD.bazel b/lib/BUILD.bazel index 5842d59d8e682..a944408e36768 100644 --- a/lib/BUILD.bazel +++ b/lib/BUILD.bazel @@ -18285,6 +18285,21 @@ java_library( visibility = ["//visibility:public"] ) +copy_file( + name = "org.jetbrains.kotlinx/kotlinx-serialization-core-jvm-1.8.1.jar_copy", + src = "@org_jetbrains_kotlinx-kotlinx-serialization-core-jvm-1_8_1_http//file", + out = "org.jetbrains.kotlinx/kotlinx-serialization-core-jvm-1.8.1.jar", + allow_symlink = True, + visibility = ["//visibility:public"] +) + +jvm_import( + name = "platform-icons-impl-intellij-jetbrains-kotlinx-serialization-core-jvm", + jar = "@org_jetbrains_kotlinx-kotlinx-serialization-core-jvm-1_8_1_http//file", + source_jar = "@org_jetbrains_kotlinx-kotlinx-serialization-core-jvm-1_8_1-sources_http//file", + visibility = ["//visibility:public"] +) + copy_file( name = "com.pngencoder/pngencoder-0.14.0.jar_copy", src = "@com_pngencoder-pngencoder-0_14_0_http//file", @@ -18728,6 +18743,13 @@ java_library( visibility = ["//visibility:public"] ) +jvm_import( + name = "platform-jewel-int_ui-standalone-jetbrains-kotlinx-serialization-core-jvm", + jar = "@org_jetbrains_kotlinx-kotlinx-serialization-core-jvm-1_8_1_http//file", + source_jar = "@org_jetbrains_kotlinx-kotlinx-serialization-core-jvm-1_8_1-sources_http//file", + visibility = ["//visibility:public"] +) + copy_file( name = "org.commonmark/commonmark-0.24.0.jar_copy", src = "@org_commonmark-commonmark-0_24_0_http//file", diff --git a/lib/MODULE.bazel b/lib/MODULE.bazel index 9046885f766d2..eaf8106e17ced 100644 --- a/lib/MODULE.bazel +++ b/lib/MODULE.bazel @@ -14524,6 +14524,20 @@ http_file( downloaded_file_path = "dbus-java-core-4.2.1-sources.jar" ) +http_file( + name = "org_jetbrains_kotlinx-kotlinx-serialization-core-jvm-1_8_1_http", + url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-core-jvm/1.8.1/kotlinx-serialization-core-jvm-1.8.1.jar", + sha256 = "3565b6d4d789bf70683c45566944287fc1d8dc75c23d98bd87d01059cc76f2b3", + downloaded_file_path = "kotlinx-serialization-core-jvm-1.8.1.jar" +) + +http_file( + name = "org_jetbrains_kotlinx-kotlinx-serialization-core-jvm-1_8_1-sources_http", + url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-core-jvm/1.8.1/kotlinx-serialization-core-jvm-1.8.1-sources.jar", + sha256 = "d4a7221a0c2ccb47bc736340344b256506cf8a5a49882883f8f56385ae69a1bc", + downloaded_file_path = "kotlinx-serialization-core-jvm-1.8.1-sources.jar" +) + http_file( name = "com_pngencoder-pngencoder-0_14_0_http", url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/com/pngencoder/pngencoder/0.14.0/pngencoder-0.14.0.jar", diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CoreModuleSets.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CoreModuleSets.kt index 2b77bae1347ae..3b73276df633d 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CoreModuleSets.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CoreModuleSets.kt @@ -270,6 +270,11 @@ object CoreModuleSets { embeddedModule("intellij.platform.analysis") embeddedModule("intellij.platform.analysis.impl") + embeddedModule("intellij.platform.icons.api") + embeddedModule("intellij.platform.icons.api.rendering") + embeddedModule("intellij.platform.icons.api.rendering.lowlevel") + embeddedModule("intellij.platform.icons.api.legacyIconSupport") + // Include minimal RPC infrastructure AFTER core platform modules // (kernel depends on platform.core, so core must be available first) moduleSet(rpcMinimal()) @@ -343,6 +348,9 @@ object CoreModuleSets { // IDE implementation (depends on lang.core, so must come after) embeddedModule("intellij.platform.ide.impl") + embeddedModule("intellij.platform.icons.impl") + embeddedModule("intellij.platform.icons.impl.intellij") + // Additional dependencies specific to lang.impl and ide.impl embeddedModule("intellij.platform.ide.concurrency") embeddedModule("intellij.platform.builtInServer") diff --git a/platform/core-ui/BUILD.bazel b/platform/core-ui/BUILD.bazel index 1f8740850206a..4ecc983bcce0c 100644 --- a/platform/core-ui/BUILD.bazel +++ b/platform/core-ui/BUILD.bazel @@ -19,6 +19,7 @@ jvm_library( "//platform/util:util-ui", "@lib//:kotlin-stdlib", "//libraries/hash4j", + "//platform/icons-api", ] ) diff --git a/platform/core-ui/intellij.platform.core.ui.iml b/platform/core-ui/intellij.platform.core.ui.iml index de297648caaaa..994b900300812 100644 --- a/platform/core-ui/intellij.platform.core.ui.iml +++ b/platform/core-ui/intellij.platform.core.ui.iml @@ -13,5 +13,6 @@ + \ No newline at end of file diff --git a/platform/core-ui/resources/intellij.platform.core.ui.xml b/platform/core-ui/resources/intellij.platform.core.ui.xml index 11fc9299a69f9..39f239d9bb192 100644 --- a/platform/core-ui/resources/intellij.platform.core.ui.xml +++ b/platform/core-ui/resources/intellij.platform.core.ui.xml @@ -3,6 +3,7 @@ + diff --git a/platform/icons-api/.gitignore b/platform/icons-api/.gitignore new file mode 100644 index 0000000000000..af272e31f1de3 --- /dev/null +++ b/platform/icons-api/.gitignore @@ -0,0 +1,111 @@ +### macOS template +# General +.DS_Store +.AppleDouble +.LSOverride + +# Thumbnails +._* + +### Gradle template +.gradle +build/ + +### Terraform template +# Local .terraform directories +**/.terraform/* + +# .tfstate files +*.tfstate +*.tfstate.* + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +*.ipr +*.iws +.idea/* +out/ +local.properties + +# IDEA/Android Studio project settings ignore exceptions +!.idea/codeStyles/ +!.idea/copyright/ +!.idea/dataSources.xml +!.idea/detekt.xml +!.idea/encodings.xml +!.idea/externalDependencies.xml +!.idea/fileTemplates/ +!.idea/icon.svg +!.idea/icon.png +!.idea/icon_dark.png +!.idea/inspectionProfiles/ +!.idea/ktfmt.xml +!.idea/ktlint.xml +!.idea/ktlint-plugin.xml +!.idea/runConfigurations/ +!.idea/scopes/ +!.idea/vcs.xml + +### Kotlin template +# Compiled class file +*.class + +# Log file +*.log + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### Windows template +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +### Misc + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Ignore IJP temp folder +/.intellijPlatform + +# Ignore release patch generator output +/this-release.txt + +# Ignore Kotlin compiler sessions +/.kotlin +/buildSrc/.kotlin +/foundation/bin/* +/samples/showcase/bin/* +/new_release_notes.md diff --git a/platform/icons-api/BUILD.bazel b/platform/icons-api/BUILD.bazel new file mode 100644 index 0000000000000..72ab58c56f3e4 --- /dev/null +++ b/platform/icons-api/BUILD.bazel @@ -0,0 +1,36 @@ +### auto-generated section `build intellij.platform.icons.api` start +load("//build:compiler-options.bzl", "create_kotlinc_options") +load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") + +create_kotlinc_options( + name = "custom_icons-api", + api_version = "2.2", + language_version = "2.2", + opt_in = [ + "org.jetbrains.icons.api.ExperimentalIconsApi", + "org.jetbrains.icons.api.InternalIconsApi", + "org.jetbrains.icons.ExperimentalIconsApi", + ], + x_x_language = [] +) + +resourcegroup( + name = "icons-api_resources", + srcs = glob(["resources/**/*"]), + strip_prefix = "resources" +) + +jvm_library( + name = "icons-api", + module_name = "intellij.platform.icons.api", + visibility = ["//visibility:public"], + srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), + resources = [":icons-api_resources"], + kotlinc_opts = ":custom_icons-api", + deps = [ + "@lib//:kotlin-stdlib", + "//libraries/kotlinx/coroutines/core", + "//libraries/kotlinx/serialization/core", + ] +) +### auto-generated section `build intellij.platform.icons.api` end \ No newline at end of file diff --git a/platform/icons-api/README.md b/platform/icons-api/README.md new file mode 100644 index 0000000000000..164be818f47ee --- /dev/null +++ b/platform/icons-api/README.md @@ -0,0 +1,72 @@ +# Cross frontend-api Icons +These Icons support being rendered by mutlple frontend-apis (swing, compose, can be extended) + +## Data & Rendering split +The API is split to two parts: +- [Data](.) +- [Rendering](./rendering) + +This allows declaring icons without depending on the rendering implementation, which is usefull on backend. + +## `org.jetbrains.icons.api.Icon` +This is data part, an Icon, which represents how the specific Icon should look like. +This should be serializable (atleast in the IntelliJ implementation) and sendable directly via RPC. +Creating this class should not generate any side-effects or long-loading of resources. + +```kotlin +val githubIcon = icon { + image("icons/github.svg", ShowcaseIcons::class.java.classLoader, modifier = IconModifier.fillMaxSize()) +} +``` + +Layouting is also supported, where you can layer more icons into one: +```kotlin +val githubIcon = imageIcon("icons/github.svg", javaClass.classLoader) +val gitIcon = imageIcon("icons/git.svg", javaClass.classLoader) + +val layeredIcon = icon { + icon(githubIcon) + icon(gitIcon) +} + +val rowLayeredIcon = icon { + column { + row { + icon(githubIcon) + icon(gitIcon) + } + row { + icon(githubIcon) + icon(gitIcon) + } + } +} +``` +Row & Column behaves similarly to Compose couterparts. +There is also IconModifiers, that can affect how the resulting Icon will look like, +again, concept from Compose, you can use modifiers to adjust: +- Layouting (margin, size, align +- Color filtering +- Svg replacements + +The Icons are going to infer expected size (based on settings, svg data or image data), +however, you can also make the icons scaled to the container component. + +## `org.jetbrains.icons.api.rendering.IconRenderer` +While having data object for Icon is great, we still need a way to render it. +This class is responsible for getting the previously mentioned Icon data object, +it figures out how to load the used images. +Creating this class actually loads resources, so it should be considered a hevy operation. + +This normally shouldn't be used directly, but rather via components, like the Jewel `fun Icon(..)` icon, or specific swing components, +that create the renderer themselves. +To create a renderer for an Icon, you should use Icon.createRenderer() function. + +## Legacy/Swing Icon interop +To use Swing icons inside the new icons/with the new api, check [legacy-icon-support](./legacy-icon-support/) + +## `org.jetbrains.icons.api.IconManager` +This interface is responsible for generating Icon models. + +## Implementation details +For implementation details, refer to [implementation modules](../icons-impl/README.md) \ No newline at end of file diff --git a/platform/icons-api/STATE.md b/platform/icons-api/STATE.md new file mode 100644 index 0000000000000..53d6148d87ee9 --- /dev/null +++ b/platform/icons-api/STATE.md @@ -0,0 +1,53 @@ + +# Icons API state + +* πŸ’» in progress +* βœ… works everywhere +* ❌ not done +* 🚢 works in compose +* πŸ‘« works in ij-compose +* 🐌 works in swing +* ❔ unsure if planned +* πŸ”§ partially done + +## Layers + +* image βœ… +* icon βœ… +* row βœ… +* column βœ… +* animation βœ… +* swingIcon βœ… +* text ❌ +* badge πŸ’» + +## Modifiers + +* align βœ… +* alpha 🚢 +* color filter 🚢 +* size (height/width) βœ… +* margin βœ… +* svg patcher πŸ‘«πŸŒ – uses legacy api patching + * filters ❌ +* stroke πŸ’» + +# Deferred Icons + +* local βœ… +* over network ❌ + +## Implementation + +* caching πŸ”§ – using legacy api atm. +* loading πŸ”§ – using legacy api atm. +* skiko svg rendering❔ +* intrinsic size calculations πŸšΆπŸ‘« +* scaling πŸšΆπŸ‘« +* blend modes πŸ”§ – only some are supported +* update/re-render dispatching πŸšΆπŸ‘« + +## Loading options +- block βœ… +- blank ❌ +- placeholder ❌ \ No newline at end of file diff --git a/platform/icons-api/build.gradle.kts b/platform/icons-api/build.gradle.kts new file mode 100644 index 0000000000000..622e6a526c586 --- /dev/null +++ b/platform/icons-api/build.gradle.kts @@ -0,0 +1,24 @@ +// This file is used by Jewel gradle script, check community/platform/jewel + +plugins { + jewel +} + +sourceSets { + main { + kotlin { + setSrcDirs(listOf("src")) + } + } + + test { + kotlin { + setSrcDirs(listOf("test")) + } + } +} + +dependencies { + api(libs.kotlinx.serialization.core) + api(libs.kotlinx.coroutines.core) +} diff --git a/platform/icons-api/intellij.platform.icons.api.iml b/platform/icons-api/intellij.platform.icons.api.iml new file mode 100644 index 0000000000000..35c78775e5aff --- /dev/null +++ b/platform/icons-api/intellij.platform.icons.api.iml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + $KOTLIN_BUNDLED$/lib/kotlinx-serialization-compiler-plugin.jar + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/platform/icons-api/legacy-icon-support/BUILD.bazel b/platform/icons-api/legacy-icon-support/BUILD.bazel new file mode 100644 index 0000000000000..5d2a02f1e0d7d --- /dev/null +++ b/platform/icons-api/legacy-icon-support/BUILD.bazel @@ -0,0 +1,33 @@ +### auto-generated section `build intellij.platform.icons.api.legacyIconSupport` start +load("//build:compiler-options.bzl", "create_kotlinc_options") +load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") + +create_kotlinc_options( + name = "custom_legacy-icon-support", + jvm_target = "11", + api_version = "2.2", + language_version = "2.2", + x_x_language = [] +) + +resourcegroup( + name = "legacy-icon-support_resources", + srcs = glob(["resources/**/*"]), + strip_prefix = "resources" +) + +jvm_library( + name = "legacy-icon-support", + module_name = "intellij.platform.icons.api.legacyIconSupport", + visibility = ["//visibility:public"], + srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), + resources = [":legacy-icon-support_resources"], + kotlinc_opts = ":custom_legacy-icon-support", + deps = [ + "@lib//:kotlin-stdlib", + "//libraries/kotlinx/coroutines/core", + "//platform/icons-api", + "//libraries/kotlinx/serialization/core", + ] +) +### auto-generated section `build intellij.platform.icons.api.legacyIconSupport` end \ No newline at end of file diff --git a/platform/icons-api/legacy-icon-support/README.md b/platform/icons-api/legacy-icon-support/README.md new file mode 100644 index 0000000000000..b0c63190eda97 --- /dev/null +++ b/platform/icons-api/legacy-icon-support/README.md @@ -0,0 +1,15 @@ +## Using new icons as swing icon +Just use `Icon.toSwingIcon()` to convert the Icon to this format. +This will actually create IconRenderer, meaning it can load images, so it is wise to reuse this and avoid recreating the Icon multiple times. +The image loader can hit cache and the result can be ok, but beware. + +This should ideally be done only in low-level comments that directly interact with swing api. + +## Using Swing icon inside new API +This is done by using `swingIcon` function, which will add new layer containing the swing icon. +There might be some non-implemented integrations related to specific behavior of some icons. +```kotlin +icon { + swingIcon(AllIcons.Actions.AddDirectory) +} +``` \ No newline at end of file diff --git a/platform/icons-api/legacy-icon-support/intellij.platform.icons.api.legacyIconSupport.iml b/platform/icons-api/legacy-icon-support/intellij.platform.icons.api.legacyIconSupport.iml new file mode 100644 index 0000000000000..8910d391c6da8 --- /dev/null +++ b/platform/icons-api/legacy-icon-support/intellij.platform.icons.api.legacyIconSupport.iml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + $KOTLIN_BUNDLED$/lib/kotlinx-serialization-compiler-plugin.jar + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/platform/icons-api/legacy-icon-support/resources/intellij.platform.icons.api.legacyIconSupport.xml b/platform/icons-api/legacy-icon-support/resources/intellij.platform.icons.api.legacyIconSupport.xml new file mode 100644 index 0000000000000..fbe842d67cff4 --- /dev/null +++ b/platform/icons-api/legacy-icon-support/resources/intellij.platform.icons.api.legacyIconSupport.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIcon.kt b/platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIcon.kt new file mode 100644 index 0000000000000..9991b9621641f --- /dev/null +++ b/platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIcon.kt @@ -0,0 +1,42 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.legacyIconSupport + +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.Icon +import org.jetbrains.icons.IconManager +import org.jetbrains.icons.design.IconDesigner +import org.jetbrains.icons.icon +import org.jetbrains.icons.modifiers.IconModifier +import org.jetbrains.icons.modifiers.fillMaxSize + +/** + * Layer for embedding swing icons. + */ +@ExperimentalIconsApi +fun IconDesigner.swingIcon(legacyIcon: javax.swing.Icon, modifier: IconModifier = IconModifier) { + custom(SwingIconLayer(legacyIcon, modifier)) +} + +/** + * Shorthand for creating a new icon from swing icon, uses IconDesigner.swingIcon(). + */ +@ExperimentalIconsApi +fun javax.swing.Icon.toNewIcon(modifier: IconModifier = IconModifier): Icon { + return icon { + swingIcon(this@toNewIcon, modifier) + } +} + +/** + * Converts specific Icon to swing Icon. + * ! This is an expensive operation and can include image loading, reuse the instance if possible. ! + */ +@ExperimentalIconsApi +fun Icon.toSwingIcon(): javax.swing.Icon { + return swingIconManager().toSwingIcon(this) +} + +@ExperimentalIconsApi +private fun swingIconManager(): SwingIconManager { + return IconManager.getInstance() as? SwingIconManager ?: error("Current IconManager doesn't implement SwingIconManager") +} \ No newline at end of file diff --git a/platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIconLayer.kt b/platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIconLayer.kt new file mode 100644 index 0000000000000..138f612554779 --- /dev/null +++ b/platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIconLayer.kt @@ -0,0 +1,15 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.legacyIconSupport + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.layers.IconLayer +import org.jetbrains.icons.modifiers.IconModifier +import javax.swing.Icon + +@ExperimentalIconsApi +@Serializable +class SwingIconLayer( + val legacyIcon: Icon, + override val modifier: IconModifier +) : IconLayer \ No newline at end of file diff --git a/platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIconManager.kt b/platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIconManager.kt new file mode 100644 index 0000000000000..14ae8fd1b3f97 --- /dev/null +++ b/platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIconManager.kt @@ -0,0 +1,10 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.legacyIconSupport + +import org.jetbrains.icons.Icon +import org.jetbrains.icons.design.IconDesigner +import org.jetbrains.icons.modifiers.IconModifier + +interface SwingIconManager { + fun toSwingIcon(icon: Icon): javax.swing.Icon +} \ No newline at end of file diff --git a/platform/icons-api/rendering/BUILD.bazel b/platform/icons-api/rendering/BUILD.bazel new file mode 100644 index 0000000000000..3572b3a4bfb63 --- /dev/null +++ b/platform/icons-api/rendering/BUILD.bazel @@ -0,0 +1,36 @@ +### auto-generated section `build intellij.platform.icons.api.rendering` start +load("//build:compiler-options.bzl", "create_kotlinc_options") +load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") + +create_kotlinc_options( + name = "custom_rendering", + api_version = "2.2", + language_version = "2.2", + opt_in = [ + "org.jetbrains.icons.api.ExperimentalIconsApi", + "org.jetbrains.icons.api.InternalIconsApi", + ], + x_x_language = [] +) + +resourcegroup( + name = "rendering_resources", + srcs = glob(["resources/**/*"]), + strip_prefix = "resources" +) + +jvm_library( + name = "rendering", + module_name = "intellij.platform.icons.api.rendering", + visibility = ["//visibility:public"], + srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), + resources = [":rendering_resources"], + kotlinc_opts = ":custom_rendering", + deps = [ + "@lib//:kotlin-stdlib", + "//libraries/kotlinx/coroutines/core", + "//platform/icons-api", + ], + exports = ["//platform/icons-api"] +) +### auto-generated section `build intellij.platform.icons.api.rendering` end \ No newline at end of file diff --git a/platform/icons-api/rendering/build.gradle.kts b/platform/icons-api/rendering/build.gradle.kts new file mode 100644 index 0000000000000..67e9c65229afc --- /dev/null +++ b/platform/icons-api/rendering/build.gradle.kts @@ -0,0 +1,24 @@ +// This file is used by Jewel gradle script, check community/platform/jewel + +plugins { + jewel +} + +sourceSets { + main { + kotlin { + setSrcDirs(listOf("src")) + } + } + + test { + kotlin { + setSrcDirs(listOf("test")) + } + } +} + +dependencies { + api(project(":jb-icons-api")) + api(libs.kotlinx.coroutines.core) +} diff --git a/platform/icons-api/rendering/intellij.platform.icons.api.rendering.iml b/platform/icons-api/rendering/intellij.platform.icons.api.rendering.iml new file mode 100644 index 0000000000000..903d7703bbe74 --- /dev/null +++ b/platform/icons-api/rendering/intellij.platform.icons.api.rendering.iml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/platform/icons-api/rendering/lowlevel/BUILD.bazel b/platform/icons-api/rendering/lowlevel/BUILD.bazel new file mode 100644 index 0000000000000..3c83479e3fd61 --- /dev/null +++ b/platform/icons-api/rendering/lowlevel/BUILD.bazel @@ -0,0 +1,31 @@ +### auto-generated section `build intellij.platform.icons.api.rendering.lowlevel` start +load("//build:compiler-options.bzl", "create_kotlinc_options") +load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") + +create_kotlinc_options( + name = "custom_lowlevel", + api_version = "2.2", + language_version = "2.2", + x_x_language = [] +) + +resourcegroup( + name = "lowlevel_resources", + srcs = glob(["resources/**/*"]), + strip_prefix = "resources" +) + +jvm_library( + name = "lowlevel", + module_name = "intellij.platform.icons.api.rendering.lowlevel", + visibility = ["//visibility:public"], + srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), + resources = [":lowlevel_resources"], + kotlinc_opts = ":custom_lowlevel", + deps = [ + "@lib//:kotlin-stdlib", + "//libraries/kotlinx/coroutines/core", + "//platform/icons-api", + ] +) +### auto-generated section `build intellij.platform.icons.api.rendering.lowlevel` end \ No newline at end of file diff --git a/platform/icons-api/rendering/lowlevel/build.gradle.kts b/platform/icons-api/rendering/lowlevel/build.gradle.kts new file mode 100644 index 0000000000000..40b56bcdd5701 --- /dev/null +++ b/platform/icons-api/rendering/lowlevel/build.gradle.kts @@ -0,0 +1,24 @@ +// This file is used by Jewel gradle script, check community/platform/jewel + +plugins { + jewel +} + +sourceSets { + main { + kotlin { + setSrcDirs(listOf("src")) + } + } + + test { + kotlin { + setSrcDirs(listOf("test")) + } + } +} + +dependencies { + api(libs.kotlinx.coroutines.core) + api(project(":jb-icons-api")) +} diff --git a/platform/icons-api/rendering/lowlevel/intellij.platform.icons.api.rendering.lowlevel.iml b/platform/icons-api/rendering/lowlevel/intellij.platform.icons.api.rendering.lowlevel.iml new file mode 100644 index 0000000000000..a4b1433e5a0e9 --- /dev/null +++ b/platform/icons-api/rendering/lowlevel/intellij.platform.icons.api.rendering.lowlevel.iml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/platform/icons-api/rendering/lowlevel/resources/intellij.platform.icons.api.rendering.lowlevel.xml b/platform/icons-api/rendering/lowlevel/resources/intellij.platform.icons.api.rendering.lowlevel.xml new file mode 100644 index 0000000000000..6191909f6f14d --- /dev/null +++ b/platform/icons-api/rendering/lowlevel/resources/intellij.platform.icons.api.rendering.lowlevel.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/platform/icons-api/rendering/lowlevel/src/org/jetbrains/icons/rendering/lowlevel/GPUImageResourceHolder.kt b/platform/icons-api/rendering/lowlevel/src/org/jetbrains/icons/rendering/lowlevel/GPUImageResourceHolder.kt new file mode 100644 index 0000000000000..1a6971020b76a --- /dev/null +++ b/platform/icons-api/rendering/lowlevel/src/org/jetbrains/icons/rendering/lowlevel/GPUImageResourceHolder.kt @@ -0,0 +1,10 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.rendering.lowlevel + +import org.jetbrains.icons.InternalIconsApi +import kotlin.reflect.KClass + +@InternalIconsApi +interface GPUImageResourceHolder { + fun getOrGenerateBitmap(bitmapClass: KClass, generator: () -> TBitmap): TBitmap +} \ No newline at end of file diff --git a/platform/icons-api/rendering/resources/intellij.platform.icons.api.rendering.xml b/platform/icons-api/rendering/resources/intellij.platform.icons.api.rendering.xml new file mode 100644 index 0000000000000..15ecc662cf97d --- /dev/null +++ b/platform/icons-api/rendering/resources/intellij.platform.icons.api.rendering.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/BitmapImageResource.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/BitmapImageResource.kt new file mode 100644 index 0000000000000..cee60b53ec323 --- /dev/null +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/BitmapImageResource.kt @@ -0,0 +1,24 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.rendering + +import org.jetbrains.icons.InternalIconsApi + +@InternalIconsApi +interface BitmapImageResource : ImageResource { + fun getRGBPixels(): IntArray + fun readPrefetchedPixel(pixels: IntArray, x: Int, y: Int): Int? + fun getBandOffsetsToSRGB(): IntArray + + override val width: Int + override val height: Int +} + +@InternalIconsApi +object EmptyBitmapImageResource : BitmapImageResource { + override fun getRGBPixels(): IntArray = intArrayOf() + override fun readPrefetchedPixel(pixels: IntArray, x: Int, y: Int): Int = 0 + override fun getBandOffsetsToSRGB(): IntArray = intArrayOf(0, 1, 2, 3) + + override val width: Int = 0 + override val height: Int = 0 +} diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/Bounds.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/Bounds.kt new file mode 100644 index 0000000000000..30b81e20db8bc --- /dev/null +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/Bounds.kt @@ -0,0 +1,85 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.rendering + +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.InternalIconsApi + +@InternalIconsApi +class Bounds( + val x: Int, + val y: Int, + width: Int, + height: Int +): Dimensions(width, height) { + + + fun copy( + x: Int = this.x, + y: Int = this.y, + width: Int = this.width, + height: Int = this.height + ): Bounds { + return Bounds( + x, + y, + width, + height + ) + } + + fun canFit(other: Bounds): Boolean = other.width <= width && other.height <= height + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + if (!super.equals(other)) return false + + other as Bounds + + if (x != other.x) return false + if (y != other.y) return false + + return true + } + + override fun hashCode(): Int { + var result = super.hashCode() + result = 31 * result + x + result = 31 * result + y + return result + } + + override fun toString(): String { + return "Bounds(x=$x, y=$y, width=$width, height=$height)" + } + + +} + +@ExperimentalIconsApi +open class Dimensions( + val width: Int, + val height: Int +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Dimensions + + if (width != other.width) return false + if (height != other.height) return false + + return true + } + + override fun hashCode(): Int { + var result = width + result = 31 * result + height + return result + } + + override fun toString(): String { + return "Dimensions(width=$width, height=$height)" + } +} \ No newline at end of file diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/IconRenderer.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/IconRenderer.kt new file mode 100644 index 0000000000000..96a62396e0e97 --- /dev/null +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/IconRenderer.kt @@ -0,0 +1,15 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.rendering + +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.Icon +import org.jetbrains.icons.InternalIconsApi + +@ExperimentalIconsApi +interface IconRenderer { + val icon: Icon + @InternalIconsApi + fun render(api: PaintingApi) + @InternalIconsApi + fun calculateExpectedDimensions(scaling: ScalingContext): Dimensions +} \ No newline at end of file diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/IconRendererManager.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/IconRendererManager.kt new file mode 100644 index 0000000000000..6d05ac66fd72e --- /dev/null +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/IconRendererManager.kt @@ -0,0 +1,59 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.rendering + +import kotlinx.coroutines.CoroutineScope +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.Icon +import java.util.ServiceLoader + +/** + * Manager for Icon renderers, use convenience methods instead (like Icon.createRenderer()) + */ +@ExperimentalIconsApi +interface IconRendererManager { + /** + * This method will create renderer for specific icon, keep in mind that this might be an expensive operation. + * Use of Icon.createRenderer() is recommended + * @param context General render context, this can be used to watch for Icon updates, or set defaults for color filters etc. + * @param loadingStrategy Dictates how the Icon loading should be performed, like block thread, show placeholder, or render blank area + */ + @ExperimentalIconsApi + fun createRenderer(icon: Icon, context: RenderingContext, loadingStrategy: LoadingStrategy = LoadingStrategy.BlockThread): IconRenderer + + fun createUpdateFlow(scope: CoroutineScope?, updateCallback: (Int) -> Unit): MutableIconUpdateFlow + + fun createRenderingContext(updateFlow: MutableIconUpdateFlow, defaultImageModifiers: ImageModifiers? = null): RenderingContext + + companion object { + @Volatile + private var instance: IconRendererManager? = null + + @JvmStatic + fun getInstance(): IconRendererManager = instance ?: loadFromSPI() + + private fun loadFromSPI(): IconRendererManager = + ServiceLoader.load(IconRendererManager::class.java).firstOrNull() + ?: error("IconRendererManager instance is not set and there is no SPI service on classpath.") + + fun createUpdateFlow(scope: CoroutineScope?, updateCallback: (Int) -> Unit): MutableIconUpdateFlow = getInstance().createUpdateFlow(scope, updateCallback) + + fun activate(manager: IconRendererManager) { + instance = manager + } + + fun createRenderingContext(updateFlow: MutableIconUpdateFlow, defaultImageModifiers: ImageModifiers? = null): RenderingContext { + return getInstance().createRenderingContext(updateFlow, defaultImageModifiers) + } + } +} + +/** + * This method will create renderer for specific icon, keep in mind that this might be an expensive operation. + * Use of Icon.createRenderer() is recommended over getting the IconRendererManager directly + * @param context General render context, this can be used to watch for Icon updates, or set defaults for color filters etc. + * @param loadingStrategy Dictates how the Icon loading should be performed, like block thread, show placeholder, or render blank area + */ +@ExperimentalIconsApi +fun Icon.createRenderer(context: RenderingContext, loadingStrategy: LoadingStrategy = LoadingStrategy.BlockThread): IconRenderer { + return IconRendererManager.getInstance().createRenderer(this, context, loadingStrategy) +} diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/IconUpdateFlow.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/IconUpdateFlow.kt new file mode 100644 index 0000000000000..aba17bb32d30d --- /dev/null +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/IconUpdateFlow.kt @@ -0,0 +1,16 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.rendering + +import kotlinx.coroutines.flow.Flow +import org.jetbrains.icons.Icon +import org.jetbrains.icons.InternalIconsApi + +@InternalIconsApi +typealias IconUpdateFlow = Flow + +@InternalIconsApi +interface MutableIconUpdateFlow: IconUpdateFlow { + fun triggerUpdate() + fun triggerDelayedUpdate(delay: Long) + fun collectDynamic(flow: Flow, handler: (Icon) -> Unit) {} +} \ No newline at end of file diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResource.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResource.kt new file mode 100644 index 0000000000000..9c311646c6fa3 --- /dev/null +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResource.kt @@ -0,0 +1,31 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.rendering + +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.InternalIconsApi +import org.jetbrains.icons.filters.ColorFilter +import org.jetbrains.icons.patchers.SvgPatcher + +@OptIn(InternalIconsApi::class) +@ExperimentalIconsApi +fun imageResource(loader: ImageResourceLoader, imageModifiers: ImageModifiers? = null): ImageResource = ImageResourceProvider.getInstance().loadImage(loader, imageModifiers) + +@ExperimentalIconsApi +interface ImageModifiers { + val colorFilter: ColorFilter? + val svgPatcher: SvgPatcher? +} + +@ExperimentalIconsApi +interface ImageResource { + /** + * Image width in pixels, if the image is rescalable this should return default size or null if default size is not set. + */ + val width: Int? + /** + * Image height in pixels, if the image is rescalable this should return default size or null if default size is not set. + */ + val height: Int? + + companion object +} \ No newline at end of file diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResourceLoader.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResourceLoader.kt new file mode 100644 index 0000000000000..ced6783c8ce4c --- /dev/null +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResourceLoader.kt @@ -0,0 +1,7 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.rendering + +import org.jetbrains.icons.InternalIconsApi + +@InternalIconsApi +interface ImageResourceLoader \ No newline at end of file diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResourceProvider.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResourceProvider.kt new file mode 100644 index 0000000000000..d62a5bf82a14b --- /dev/null +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResourceProvider.kt @@ -0,0 +1,26 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.rendering + +import org.jetbrains.icons.InternalIconsApi +import java.util.ServiceLoader + +@InternalIconsApi +interface ImageResourceProvider { + fun loadImage(loader: ImageResourceLoader, imageModifiers: ImageModifiers? = null): ImageResource + + companion object { + @Volatile + private var instance: ImageResourceProvider? = null + + @JvmStatic + fun getInstance(): ImageResourceProvider = instance ?: loadFromSPI() + + private fun loadFromSPI(): ImageResourceProvider = + ServiceLoader.load(ImageResourceProvider::class.java).firstOrNull() + ?: error("ImageResourceProvider instance is not set and there is no SPI service on classpath.") + + fun activate(provider: ImageResourceProvider) { + instance = provider + } + } +} \ No newline at end of file diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/LoadingOptions.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/LoadingOptions.kt new file mode 100644 index 0000000000000..25e5af428ea93 --- /dev/null +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/LoadingOptions.kt @@ -0,0 +1,17 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.rendering + +import org.jetbrains.icons.ExperimentalIconsApi + +@ExperimentalIconsApi +sealed interface LoadingStrategy { + class RenderBlank( + val dimensions: Dimensions + ) : LoadingStrategy + + class RenderPlaceholder( + val placeHolder: IconRenderer + ) : LoadingStrategy + + object BlockThread : LoadingStrategy +} \ No newline at end of file diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/PaintingApi.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/PaintingApi.kt new file mode 100644 index 0000000000000..50fe84a885ba9 --- /dev/null +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/PaintingApi.kt @@ -0,0 +1,47 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.rendering + +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.InternalIconsApi +import org.jetbrains.icons.design.Color +import org.jetbrains.icons.filters.ColorFilter + +/** + * Abstraction of painting API, this is used to define icons or graphics that are customizable + * but also reusable between different environments, where graphic api differ. + */ +@InternalIconsApi +interface PaintingApi { + val bounds: Bounds + val scaling: ScalingContext + @OptIn(ExperimentalIconsApi::class) + fun drawImage( + image: ImageResource, + x: Int = 0, + y: Int = 0, + width: Int? = null, + height: Int? = null, + srcX: Int = 0, + srcY: Int = 0, + srcWidth: Int? = null, + srcHeight: Int? = null, + alpha: Float = 1.0f, + colorFilter: ColorFilter? = null + ) + fun drawCircle(color: Color, x: Int, y: Int, radius: Float, alpha: Float = 1f, mode: DrawMode = DrawMode.Fill) + fun drawRect(color: Color, x: Int, y: Int, width: Int, height: Int, alpha: Float = 1f, mode: DrawMode = DrawMode.Fill) + fun getUsedBounds(): Bounds + fun withCustomContext(bounds: Bounds, overrideColorFilter: ColorFilter? = null): PaintingApi +} + +@InternalIconsApi +enum class DrawMode { + Fill, + Clear, + Stroke +} + +@InternalIconsApi +interface ScalingContext { + val display: Float +} \ No newline at end of file diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/RenderingContext.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/RenderingContext.kt new file mode 100644 index 0000000000000..8886f5b3fe16f --- /dev/null +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/RenderingContext.kt @@ -0,0 +1,30 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.rendering + +import org.jetbrains.icons.ExperimentalIconsApi + +/** + * Rendering context affects the behavior of the actual renderers and can be used to, for example, pass update callbacks. + */ +@ExperimentalIconsApi +class RenderingContext( + /** + * Update flow notifies the Icon renderer user about the need to force-rerender the Icon if necessary. (for example on animation frame) + */ + val updateFlow: MutableIconUpdateFlow, + /** + * Default image modifiers that will be applied to the Icon renderer, can be used to set default color filters etc. + * This is mainly used when Icon is layered into another Icon, to pass the color filters etc., but can be also used + * to pass the filters from the component Icon should be rendered in. + */ + val defaultImageModifiers: ImageModifiers? = null +) { + fun copy(updateFlow: MutableIconUpdateFlow? = null, defaultImageModifiers: ImageModifiers? = null): RenderingContext = RenderingContext( + updateFlow ?: this.updateFlow, + defaultImageModifiers ?: this.defaultImageModifiers + ) + + companion object { + val Empty: RenderingContext = RenderingContext(IconRendererManager.createUpdateFlow(null) { }) + } +} \ No newline at end of file diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/RescalableImageResource.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/RescalableImageResource.kt new file mode 100644 index 0000000000000..0941de88650b3 --- /dev/null +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/RescalableImageResource.kt @@ -0,0 +1,64 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.rendering + +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.InternalIconsApi + +@OptIn(ExperimentalIconsApi::class) +@InternalIconsApi +interface RescalableImageResource : ImageResource { + fun scale(scale: ImageScale): BitmapImageResource + fun calculateExpectedDimensions(scale: ImageScale): Bounds +} + +@InternalIconsApi +sealed interface ImageScale { + fun calculateScalingFactorByOriginalDimensions(width: Int, height: Int? = null): Float +} + +@InternalIconsApi +class FixedScale(val scale: Float) : ImageScale { + override fun calculateScalingFactorByOriginalDimensions(width: Int, height: Int?): Float = scale + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as FixedScale + + return scale == other.scale + } + + override fun hashCode(): Int { + return scale.hashCode() + } +} + +@InternalIconsApi +class FitAreaScale(val width: Int, val height: Int) : ImageScale { + override fun calculateScalingFactorByOriginalDimensions(width: Int, height: Int?): Float { + val scale = this.width / width.toFloat() + if (height != null) { + return minOf(scale, this.height / height.toFloat()) + } + return scale + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as FitAreaScale + + if (width != other.width) return false + if (height != other.height) return false + + return true + } + + override fun hashCode(): Int { + var result = width + result = 31 * result + height + return result + } +} diff --git a/platform/icons-api/resources/intellij.platform.icons.api.xml b/platform/icons-api/resources/intellij.platform.icons.api.xml new file mode 100644 index 0000000000000..43a84f880b838 --- /dev/null +++ b/platform/icons-api/resources/intellij.platform.icons.api.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/platform/icons-api/src/org/jetbrains/icons/DynamicIcon.kt b/platform/icons-api/src/org/jetbrains/icons/DynamicIcon.kt new file mode 100644 index 0000000000000..6484e70925351 --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/DynamicIcon.kt @@ -0,0 +1,11 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons + +import kotlinx.coroutines.flow.Flow + +@ExperimentalIconsApi +interface DynamicIcon: Icon { + fun getCurrentIcon(): Icon + suspend fun swap(icon: Icon) + fun getFlow(): Flow +} diff --git a/platform/icons-api/src/org/jetbrains/icons/ExperimentalIconsApi.kt b/platform/icons-api/src/org/jetbrains/icons/ExperimentalIconsApi.kt new file mode 100644 index 0000000000000..0e82b6d6b0da1 --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/ExperimentalIconsApi.kt @@ -0,0 +1,26 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons + +@RequiresOptIn( + level = RequiresOptIn.Level.WARNING, + message = "This is an experimental API and is likely to change before becoming stable.", +) +@Target( + AnnotationTarget.CLASS, + AnnotationTarget.CONSTRUCTOR, + AnnotationTarget.FIELD, + AnnotationTarget.FUNCTION, + AnnotationTarget.PROPERTY, + AnnotationTarget.PROPERTY_GETTER, + AnnotationTarget.PROPERTY_SETTER, + AnnotationTarget.TYPEALIAS, + AnnotationTarget.VALUE_PARAMETER, +) +/** + * APIs annotated as experimental are subject to change at any time, with no binary nor source compatibility + * guarantees. Behavior might change at any time. + * + * Using any API annotated as experimental in client code should be done with caution, and you will have to take care of + * breakages in your code when usages are impacted by a change. + */ +annotation class ExperimentalIconsApi \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/Icon.kt b/platform/icons-api/src/org/jetbrains/icons/Icon.kt new file mode 100644 index 0000000000000..82b44683c8cc1 --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/Icon.kt @@ -0,0 +1,18 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Polymorphic +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +/** + * This is universal Icon interface that can be used across different environments with different graphics api. + * (or without one) + * + * For serialization, use getSerializersModule() on IconManager + */ +@ExperimentalIconsApi +interface Icon \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/IconManager.kt b/platform/icons-api/src/org/jetbrains/icons/IconManager.kt new file mode 100644 index 0000000000000..eb2f09616e7d0 --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/IconManager.kt @@ -0,0 +1,68 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.modules.SerializersModule +import org.jetbrains.icons.design.IconDesigner +import org.jetbrains.icons.modifiers.IconModifier +import java.util.ServiceLoader + +@ExperimentalIconsApi +interface IconManager { + /** + * Creates new Icon "description", this is a cheap operation, to render the Icon, use Icon.createRenderer() function. + * Use convenience top-level method icon {} instead if possible. + */ + fun icon(designer: IconDesigner.() -> Unit): Icon + + /** + * Dynamic icon allows dynamic icon swapping after the Icon model was created, the renderers created from such + * icon should be able to listen to these changes and swap the actual rendered icon on-the-fly. + */ + fun dynamicIcon(icon: Icon): DynamicIcon + + fun getSerializersModule(): SerializersModule + + companion object { + @Volatile + private var instance: IconManager? = null + + @JvmStatic + fun getInstance(): IconManager = instance ?: loadFromSPI() + + private fun loadFromSPI(): IconManager = + ServiceLoader.load(IconManager::class.java).firstOrNull() + ?: error("IconManager instance is not set and there is no SPI service on classpath.") + + + fun activate(manager: IconManager) { + instance = manager + } + } +} + +/** + * Creates new Icon + * The result should be serializable and contains description of what the Icon should look like. + * + * To render the Icon, renderer has to be obtained first using createRenderer(), however this should be only done + * from inside components. (check intellij.platform.icons.api.rendering module), for usage inside swing, + * check intellij.platform.icons.api.legacyIconSupport module. + * + * Usage: + * ''' + * icon { + * image("icons/icon.svg", MyClass::class.java.classLoader) + * } + * ''' + * + * Check the designer interface for layer options. Also check intellij.platform.icons.api.legacyIconSupport module + * to find out how to convert old icons and new icons. + */ +fun icon(designer: IconDesigner.() -> Unit): Icon = IconManager.getInstance().icon(designer) + +fun dynamicIcon(designer: IconDesigner.() -> Unit): DynamicIcon = IconManager.getInstance().dynamicIcon(icon(designer)) + +fun dynamicIcon(icon: Icon): DynamicIcon = IconManager.getInstance().dynamicIcon(icon) + +fun imageIcon(path: String, classLoader: ClassLoader? = null, modifier: IconModifier = IconModifier): Icon = icon { image(path, classLoader, modifier) } \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/InternalIconsApi.kt b/platform/icons-api/src/org/jetbrains/icons/InternalIconsApi.kt new file mode 100644 index 0000000000000..f5fa9ef1b67d7 --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/InternalIconsApi.kt @@ -0,0 +1,23 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons + +@RequiresOptIn( + level = RequiresOptIn.Level.WARNING, + message = "This is an internal API and is subject to change without notice.", +) +@Target( + AnnotationTarget.CLASS, + AnnotationTarget.CONSTRUCTOR, + AnnotationTarget.FIELD, + AnnotationTarget.FUNCTION, + AnnotationTarget.PROPERTY, + AnnotationTarget.PROPERTY_GETTER, + AnnotationTarget.PROPERTY_SETTER, + AnnotationTarget.TYPEALIAS, + AnnotationTarget.VALUE_PARAMETER, +) +/** + * APIs annotated as internal are not meant for usage in client code; there are no guarantees about the binary + * nor source compatibility, and the behavior can change at any time. **Do not use** these APIs in client code! + */ +annotation class InternalIconsApi \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/design/BlendMode.kt b/platform/icons-api/src/org/jetbrains/icons/design/BlendMode.kt new file mode 100644 index 0000000000000..5418ea3b801c2 --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/design/BlendMode.kt @@ -0,0 +1,16 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.design + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.ExperimentalIconsApi + +@ExperimentalIconsApi +@Serializable +enum class BlendMode { + SrcIn, + Hue, + Saturation, + Luminosity, + Color, + Multiply +} \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/design/Color.kt b/platform/icons-api/src/org/jetbrains/icons/design/Color.kt new file mode 100644 index 0000000000000..cc71870f78391 --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/design/Color.kt @@ -0,0 +1,82 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.design + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.ExperimentalIconsApi +import kotlin.math.roundToInt + +@ExperimentalIconsApi +@Serializable +sealed interface Color { + fun toHex(): String + + companion object { + val Transparent: Color = RGBA(0f, 0f, 0f, 0f) + } +} + + +@ExperimentalIconsApi +@Serializable +class RGBA( + val red: Float, + val green: Float, + val blue: Float, + val alpha: Float +): Color { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as RGBA + + if (red != other.red) return false + if (green != other.green) return false + if (blue != other.blue) return false + if (alpha != other.alpha) return false + + return true + } + + override fun hashCode(): Int { + var result = red.hashCode() + result = 31 * result + green.hashCode() + result = 31 * result + blue.hashCode() + result = 31 * result + alpha.hashCode() + return result + } + + override fun toString(): String { + return "RGBA(red=$red, green=$green, blue=$blue, alpha=$alpha)" + } + + override fun toHex(): String { + val r = Integer.toHexString((red * 255).roundToInt()) + val g = Integer.toHexString((green * 255).roundToInt()) + val b = Integer.toHexString((blue * 255).roundToInt()) + val intAlpha = (alpha * 255).roundToInt() + + return formatColorRgbaHexString(r, g, b, intAlpha, true, true) + } + + private fun formatColorRgbaHexString( + rString: String, + gString: String, + bString: String, + alphaInt: Int, + includeHashSymbol: Boolean, + omitAlphaWhenFullyOpaque: Boolean, + ): String = buildString { + if (includeHashSymbol) append('#') + + append(rString.padStart(2, '0')) + append(gString.padStart(2, '0')) + append(bString.padStart(2, '0')) + + if (alphaInt < 255 || !omitAlphaWhenFullyOpaque) { + val a = Integer.toHexString(alphaInt) + append(a.padStart(2, '0')) + } + } + +} \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/design/IconAlign.kt b/platform/icons-api/src/org/jetbrains/icons/design/IconAlign.kt new file mode 100644 index 0000000000000..286d3af4defb7 --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/design/IconAlign.kt @@ -0,0 +1,61 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.design + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.ExperimentalIconsApi + +@Suppress("unused") +@Serializable +@ExperimentalIconsApi +class IconAlign( + val verticalAlign: IconVerticalAlign, + val horizontalAlign: IconHorizontalAlign +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as IconAlign + + if (verticalAlign != other.verticalAlign) return false + if (horizontalAlign != other.horizontalAlign) return false + + return true + } + + override fun hashCode(): Int { + var result = verticalAlign.hashCode() + result = 31 * result + horizontalAlign.hashCode() + return result + } + + override fun toString(): String { + return "IconAlign(verticalAlign=$verticalAlign, horizontalAlign=$horizontalAlign)" + } + + companion object { + val TopLeft: IconAlign = IconAlign(IconVerticalAlign.Top, IconHorizontalAlign.Left) + val TopCenter: IconAlign = IconAlign(IconVerticalAlign.Top, IconHorizontalAlign.Center) + val TopRight: IconAlign = IconAlign(IconVerticalAlign.Top, IconHorizontalAlign.Right) + val CenterLeft: IconAlign = IconAlign(IconVerticalAlign.Center, IconHorizontalAlign.Left) + val Center: IconAlign = IconAlign(IconVerticalAlign.Center, IconHorizontalAlign.Center) + val CenterRight: IconAlign = IconAlign(IconVerticalAlign.Center, IconHorizontalAlign.Right) + val BottomLeft: IconAlign = IconAlign(IconVerticalAlign.Bottom, IconHorizontalAlign.Left) + val BottomCenter: IconAlign = IconAlign(IconVerticalAlign.Bottom, IconHorizontalAlign.Center) + val BottomRight: IconAlign = IconAlign(IconVerticalAlign.Bottom, IconHorizontalAlign.Right) + } +} + +@Serializable +enum class IconVerticalAlign { + Top, + Center, + Bottom +} + +@Serializable +enum class IconHorizontalAlign { + Left, + Center, + Right +} \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/design/IconDesigner.kt b/platform/icons-api/src/org/jetbrains/icons/design/IconDesigner.kt new file mode 100644 index 0000000000000..07eb076c3814c --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/design/IconDesigner.kt @@ -0,0 +1,46 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.design + +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.Icon +import org.jetbrains.icons.layers.IconLayer +import org.jetbrains.icons.modifiers.IconModifier +import org.jetbrains.icons.modifiers.align +import org.jetbrains.icons.modifiers.cutoutMargin +import org.jetbrains.icons.modifiers.size + +/** + * Individual methods act as layers and the order dictates in which order they are rendered. + * + * @param IconModifier Modifications that should be performed on the Layer, like sizing, margin, color filters etc. (order-dependant) + */ +@ExperimentalIconsApi +interface IconDesigner { + fun image(path: String, classLoader: ClassLoader? = null, modifier: IconModifier = IconModifier) + fun icon(icon: Icon, modifier: IconModifier = IconModifier) + fun row(spacing: IconUnit = 0.px, modifier: IconModifier = IconModifier, builder: IconDesigner.() -> Unit) + fun column(spacing: IconUnit = 0.px, modifier: IconModifier = IconModifier, builder: IconDesigner.() -> Unit) + fun animation(modifier: IconModifier = IconModifier, builder: IconAnimationDesigner.() -> Unit) + fun shape(color: Color, shape: Shape, modifier: IconModifier) + /** + * Adds custom layer type to this designer, keep in mind that additional registration of serializers/renderers is needed + * for the layer to be used. Check the specific Icon Manager used to see registration details. + */ + fun custom(iconLayer: IconLayer) +} + +fun IconDesigner.badge( + color: Color, + shape: Shape, + size: IconUnit = (3.5 * 2).dp relativeTo 20.dp, + align: IconAlign = IconAlign.TopRight, + cutout: IconUnit = 1.5.dp relativeTo 20.dp, + modifier: IconModifier = IconModifier, +) { + shape(color, shape, modifier.size(size).align(align).cutoutMargin(cutout)) +} + +@ExperimentalIconsApi +interface IconAnimationDesigner { + fun frame(duration: Long, builder: IconDesigner.() -> Unit) +} \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/design/IconMargin.kt b/platform/icons-api/src/org/jetbrains/icons/design/IconMargin.kt new file mode 100644 index 0000000000000..b2c4c52fde8c9 --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/design/IconMargin.kt @@ -0,0 +1,44 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.design + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.ExperimentalIconsApi + +@Serializable +@ExperimentalIconsApi +class IconMargin( + val top: IconUnit, + val left: IconUnit, + val bottom: IconUnit, + val right: IconUnit, +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as IconMargin + + if (top != other.top) return false + if (left != other.left) return false + if (bottom != other.bottom) return false + if (right != other.right) return false + + return true + } + + override fun hashCode(): Int { + var result = top.hashCode() + result = 31 * result + left.hashCode() + result = 31 * result + bottom.hashCode() + result = 31 * result + right.hashCode() + return result + } + + override fun toString(): String { + return "IconMargin(top=$top, left=$left, bottom=$bottom, right=$right)" + } + + companion object { + val Zero: IconMargin = IconMargin(0.dp, 0.dp, 0.dp, 0.dp) + } +} \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/design/IconUnits.kt b/platform/icons-api/src/org/jetbrains/icons/design/IconUnits.kt new file mode 100644 index 0000000000000..29f19d6082ee6 --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/design/IconUnits.kt @@ -0,0 +1,153 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.design + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.ExperimentalIconsApi + +/** + * Samples: + *
+ *   20.px
+ *  * 100.percent
+ *  * 0.5.fraction
+ *  * 5.dp
+ *  * AutoIconUnit - fills max width
+ * 
+ */ +@Serializable +@ExperimentalIconsApi +sealed interface IconUnit { + companion object { + val Zero: IconUnit = 0.dp + val Auto: IconUnit = MaxIconUnit + } +} + +@Serializable +@ExperimentalIconsApi +class DisplayPointIconUnit(val value: Double) : IconUnit { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as DisplayPointIconUnit + + return value == other.value + } + + override fun hashCode(): Int { + return value.hashCode() + } + + override fun toString(): String { + return "DisplayPointIconUnit(value=$value)" + } + +} + +@Serializable +@ExperimentalIconsApi +class PixelIconUnit(val value: Int) : IconUnit { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as PixelIconUnit + + return value == other.value + } + + override fun hashCode(): Int { + return value + } + + override fun toString(): String { + return "PixelIconUnit(value=$value)" + } +} + +@Serializable +@ExperimentalIconsApi +class PercentIconUnit(val value: Double) : IconUnit { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as PercentIconUnit + + return value == other.value + } + + override fun hashCode(): Int { + return value.hashCode() + } + + override fun toString(): String { + return "PercentIconUnit(value=$value)" + } +} + +@Serializable +@ExperimentalIconsApi +object MaxIconUnit : IconUnit + +val Int.dp: DisplayPointIconUnit + get() = DisplayPointIconUnit(this.toDouble()) + +val Double.dp: DisplayPointIconUnit + get() = DisplayPointIconUnit(this) + +val Float.dp: DisplayPointIconUnit + get() = DisplayPointIconUnit(this.toDouble()) + +val Int.px: PixelIconUnit + get() = PixelIconUnit(this) + +/** + * The resulting value is divided by 100, so the number before this represents actual percentage (not fraction) + * @see fraction + */ +val Int.percent: PercentIconUnit + get() = this.toDouble().percent + +/** + * The resulting value is divided by 100, so the number before this represents actual percentage (not fraction) + * @see fraction + */ +val Double.percent: PercentIconUnit + get() = PercentIconUnit(this / 100.0) + +/** + * The resulting value is divided by 100, so the number before this represents actual percentage (not fraction) + * @see fraction + */ +val Float.percent: PercentIconUnit + get() = this.toDouble().percent +/** + * The resulting value is not divided by 100, acts as a percentage of bounds + * @see percent + */ +val Int.fraction: PercentIconUnit + get() = this.toDouble().fraction + +/** + * The resulting value is not divided by 100, acts as a percentage of bounds + * @see percent + */ +val Double.fraction: PercentIconUnit + get() = PercentIconUnit(this) + +/** + * The resulting value is not divided by 100, acts as a percentage of bounds + * @see percent + */ +val Float.fraction: PercentIconUnit + get() = this.toDouble().fraction + +infix fun PixelIconUnit.relativeTo(bound: PixelIconUnit): PercentIconUnit { + return (this.value / bound.value).fraction +} + +infix fun DisplayPointIconUnit.relativeTo(bound: DisplayPointIconUnit): PercentIconUnit { + return (this.value / bound.value).fraction +} \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/design/Shape.kt b/platform/icons-api/src/org/jetbrains/icons/design/Shape.kt new file mode 100644 index 0000000000000..8565f5616ab75 --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/design/Shape.kt @@ -0,0 +1,27 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.design + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.ExperimentalIconsApi + +@ExperimentalIconsApi +@Serializable +sealed interface Shape { + companion object { + } +} + +@ExperimentalIconsApi +@Serializable +object Circle: Shape { + override fun toString(): String { + return "Circle" + } + +} + +@ExperimentalIconsApi +@Serializable +object Rectangle: Shape { + +} \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/design/SvgPatcherDesigner.kt b/platform/icons-api/src/org/jetbrains/icons/design/SvgPatcherDesigner.kt new file mode 100644 index 0000000000000..f0e1087b58b6f --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/design/SvgPatcherDesigner.kt @@ -0,0 +1,56 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.design + +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.InternalIconsApi +import org.jetbrains.icons.patchers.SvgPatchOperation +import org.jetbrains.icons.patchers.SvgPatcher +import org.jetbrains.icons.patchers.SvgPathFilteredOperations + +@ExperimentalIconsApi +public class SvgPatcherDesigner { + private val operations = mutableListOf() + private val filteredOperations = mutableListOf() + + fun replace(name: String, newValue: String) { + operations.add(SvgPatchOperation(name, newValue, false, false, null, SvgPatchOperation.Operation.Replace)) + } + + fun replaceIfMatches(name: String, expectedValue: String, newValue: String) { + operations.add(SvgPatchOperation(name, newValue, true, false, expectedValue, SvgPatchOperation.Operation.Replace)) + } + + fun replaceUnlessMatches(name: String, expectedValue: String, newValue: String) { + operations.add(SvgPatchOperation(name, newValue, true, true, expectedValue, SvgPatchOperation.Operation.Replace)) + } + + fun removeIfMatches(name: String, expectedValue: String) { + operations.add(SvgPatchOperation(name, null, true, false, expectedValue, SvgPatchOperation.Operation.Remove)) + } + + fun removeUnlessMatches(name: String, expectedValue: String) { + operations.add(SvgPatchOperation(name, null, true, true, expectedValue, SvgPatchOperation.Operation.Remove)) + } + + fun remove(name: String) { + operations.add(SvgPatchOperation(name, null, false, false, null, SvgPatchOperation.Operation.Remove)) + } + + fun set(name: String, value: String) { + operations.add(SvgPatchOperation(name, value, false, false, null, SvgPatchOperation.Operation.Add)) + } + + fun add(name: String, value: String) { + operations.add(SvgPatchOperation(name, value, false, false, null, SvgPatchOperation.Operation.Add)) + } + + fun filter(path: String, svgPatcherDesigner: SvgPatcherDesigner.() -> Unit) { + val designer = SvgPatcherDesigner() + svgPatcherDesigner.invoke(designer) + // TODO Support recursive filters + filteredOperations.add(SvgPathFilteredOperations(path, designer.build().operations)) + } + + @InternalIconsApi + internal fun build(): SvgPatcher = SvgPatcher(operations, filteredOperations) +} \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/filters/ColorFilter.kt b/platform/icons-api/src/org/jetbrains/icons/filters/ColorFilter.kt new file mode 100644 index 0000000000000..337fc16bb030a --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/filters/ColorFilter.kt @@ -0,0 +1,9 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.filters + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.ExperimentalIconsApi + +@ExperimentalIconsApi +@Serializable +sealed interface ColorFilter \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/filters/TintColorFilter.kt b/platform/icons-api/src/org/jetbrains/icons/filters/TintColorFilter.kt new file mode 100644 index 0000000000000..ada13b39f3c8a --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/filters/TintColorFilter.kt @@ -0,0 +1,15 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.filters + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.design.BlendMode +import org.jetbrains.icons.design.Color + +@ExperimentalIconsApi +@Serializable +class TintColorFilter( + val color: Color, + val blendMode: BlendMode = BlendMode.SrcIn +): ColorFilter { +} diff --git a/platform/icons-api/src/org/jetbrains/icons/layers/IconLayer.kt b/platform/icons-api/src/org/jetbrains/icons/layers/IconLayer.kt new file mode 100644 index 0000000000000..f495b2a819ce6 --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/layers/IconLayer.kt @@ -0,0 +1,13 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.layers + +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.modifiers.IconModifier + +/** + * To serialize IconLayer, serializersModule from IconManager might be used. + */ +@ExperimentalIconsApi +interface IconLayer { + val modifier: IconModifier +} \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/modifiers/AlignIconModifier.kt b/platform/icons-api/src/org/jetbrains/icons/modifiers/AlignIconModifier.kt new file mode 100644 index 0000000000000..39cca93235ae0 --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/modifiers/AlignIconModifier.kt @@ -0,0 +1,33 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.modifiers + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.design.IconAlign + +@Serializable +@ExperimentalIconsApi +class AlignIconModifier( + val align: IconAlign +): IconModifier { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as AlignIconModifier + + return align == other.align + } + + override fun hashCode(): Int { + return align.hashCode() + } + + override fun toString(): String { + return "AlignIconModifier(align=$align)" + } +} + +fun IconModifier.align(align: IconAlign): IconModifier { + return this then AlignIconModifier(align) +} diff --git a/platform/icons-api/src/org/jetbrains/icons/modifiers/AlphaIconModifier.kt b/platform/icons-api/src/org/jetbrains/icons/modifiers/AlphaIconModifier.kt new file mode 100644 index 0000000000000..635462047ec8b --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/modifiers/AlphaIconModifier.kt @@ -0,0 +1,32 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.modifiers + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.ExperimentalIconsApi + +@Serializable +@ExperimentalIconsApi +class AlphaIconModifier( + val alpha: Float +): IconModifier { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as AlphaIconModifier + + return alpha == other.alpha + } + + override fun hashCode(): Int { + return alpha.hashCode() + } + + override fun toString(): String { + return "AlphaIconModifier(alpha=$alpha)" + } +} + +fun IconModifier.alpha(alpha: Float): IconModifier { + return this then AlphaIconModifier(alpha) +} \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/modifiers/ColorFilterModifier.kt b/platform/icons-api/src/org/jetbrains/icons/modifiers/ColorFilterModifier.kt new file mode 100644 index 0000000000000..dea79c0adba76 --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/modifiers/ColorFilterModifier.kt @@ -0,0 +1,41 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.modifiers + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.design.BlendMode +import org.jetbrains.icons.design.Color +import org.jetbrains.icons.filters.ColorFilter +import org.jetbrains.icons.filters.TintColorFilter + +@Serializable +@ExperimentalIconsApi +class ColorFilterModifier( + val colorFilter: ColorFilter +): IconModifier { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ColorFilterModifier + + return colorFilter == other.colorFilter + } + + override fun hashCode(): Int { + return colorFilter.hashCode() + } + + override fun toString(): String { + return "ColorFilterModifier(colorFilter=$colorFilter)" + } + +} + +fun IconModifier.colorFilter(colorFilter: ColorFilter): IconModifier { + return this then ColorFilterModifier(colorFilter) +} + +fun IconModifier.tintColor(color: Color, blendMode: BlendMode): IconModifier { + return colorFilter(TintColorFilter(color, blendMode)) +} diff --git a/platform/icons-api/src/org/jetbrains/icons/modifiers/CombinedIconModifier.kt b/platform/icons-api/src/org/jetbrains/icons/modifiers/CombinedIconModifier.kt new file mode 100644 index 0000000000000..09ccccc2cdef1 --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/modifiers/CombinedIconModifier.kt @@ -0,0 +1,35 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.modifiers + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.ExperimentalIconsApi + +@Serializable +@ExperimentalIconsApi +class CombinedIconModifier( + val root: IconModifier, + val other: IconModifier, +): IconModifier { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as CombinedIconModifier + + if (root != other.root) return false + if (other != other.other) return false + + return true + } + + override fun hashCode(): Int { + var result = root.hashCode() + result = 31 * result + other.hashCode() + return result + } + + override fun toString(): String { + return "CombinedIconModifier(root=$root, other=$other)" + } + +} \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/modifiers/CutoutMarginModifier.kt b/platform/icons-api/src/org/jetbrains/icons/modifiers/CutoutMarginModifier.kt new file mode 100644 index 0000000000000..d83f6cd0bab50 --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/modifiers/CutoutMarginModifier.kt @@ -0,0 +1,38 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.modifiers + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.design.IconUnit + +@Serializable +@ExperimentalIconsApi +/** + * Add cutout margin to the specific layer, which will clear the surrounding area + * Currently supported only by shape and image layers, image layer will not consider + * internal svg shape and will cut out rectangular area. + */ +class CutoutMarginModifier( + val size: IconUnit +): IconModifier { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as CutoutMarginModifier + + return size == other.size + } + + override fun hashCode(): Int { + return size.hashCode() + } + + override fun toString(): String { + return "CutoutMarginModifier(size=$size)" + } +} + +fun IconModifier.cutoutMargin(size: IconUnit): IconModifier { + return this then CutoutMarginModifier(size) +} \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/modifiers/HeightIconModifier.kt b/platform/icons-api/src/org/jetbrains/icons/modifiers/HeightIconModifier.kt new file mode 100644 index 0000000000000..7998ba53f1d37 --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/modifiers/HeightIconModifier.kt @@ -0,0 +1,42 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.modifiers + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.design.MaxIconUnit +import org.jetbrains.icons.design.IconUnit + +@Serializable +@ExperimentalIconsApi +class HeightIconModifier( + val height: IconUnit +): IconModifier { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as HeightIconModifier + + return height == other.height + } + + override fun hashCode(): Int { + return height.hashCode() + } + + override fun toString(): String { + return "HeightIconModifier(height=$height)" + } +} + +fun IconModifier.height(height: IconUnit): IconModifier { + return this then HeightIconModifier(height) +} + +fun IconModifier.size(size: IconUnit): IconModifier { + return this.width(size).height(size) +} + +fun IconModifier.fillMaxSize(): IconModifier { + return this.size(MaxIconUnit) +} \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/modifiers/IconModifier.kt b/platform/icons-api/src/org/jetbrains/icons/modifiers/IconModifier.kt new file mode 100644 index 0000000000000..498781061e317 --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/modifiers/IconModifier.kt @@ -0,0 +1,37 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.modifiers + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.serializer +import org.jetbrains.icons.ExperimentalIconsApi + +/** + * Modifications that should be performed on the Layer, like sizing, margin, color filters etc. (order-dependant) + * + * Sample usage: + * ''' + * icon { + * image("icons/icon.svg", IconModifier.margin(20.px)) + * } + * ''' + * + * @see AlignIconModifier + * @see MarginIconModifier + * @see HeightIconModifier + * @see WidthIconModifier + */ +@Serializable +@ExperimentalIconsApi +sealed interface IconModifier { + companion object: IconModifier +} + +/** + * Concatenates this modifier with another. + * + * Returns a [IconModifier] representing this modifier followed by [other] in sequence. + */ +@ExperimentalIconsApi +infix fun IconModifier.then(other: IconModifier): IconModifier = + if (other === IconModifier) this else CombinedIconModifier(this, other) \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/modifiers/MarginIconModifier.kt b/platform/icons-api/src/org/jetbrains/icons/modifiers/MarginIconModifier.kt new file mode 100644 index 0000000000000..1cee61c1f7446 --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/modifiers/MarginIconModifier.kt @@ -0,0 +1,56 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.modifiers + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.design.IconUnit + +@Serializable +@ExperimentalIconsApi +class MarginIconModifier( + val left: IconUnit, + val top: IconUnit, + val right: IconUnit, + val bottom: IconUnit +): IconModifier { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as MarginIconModifier + + if (left != other.left) return false + if (top != other.top) return false + if (right != other.right) return false + if (bottom != other.bottom) return false + + return true + } + + override fun hashCode(): Int { + var result = left.hashCode() + result = 31 * result + top.hashCode() + result = 31 * result + right.hashCode() + result = 31 * result + bottom.hashCode() + return result + } + + override fun toString(): String { + return "MarginIconModifier(left=$left, top=$top, right=$right, bottom=$bottom)" + } +} + +@ExperimentalIconsApi +fun IconModifier.margin(left: IconUnit, top: IconUnit, right: IconUnit, bottom: IconUnit): IconModifier { + return this then MarginIconModifier(left, top, right, bottom) +} + +@ExperimentalIconsApi +fun IconModifier.margin(all: IconUnit): IconModifier { + return margin(all, all, all, all) +} + +@ExperimentalIconsApi +fun IconModifier.margin(vertical: IconUnit, horizontal: IconUnit): IconModifier { + return margin(horizontal, vertical, horizontal, vertical) +} \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/modifiers/StrokeModifier.kt b/platform/icons-api/src/org/jetbrains/icons/modifiers/StrokeModifier.kt new file mode 100644 index 0000000000000..c737a1fcbd0d5 --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/modifiers/StrokeModifier.kt @@ -0,0 +1,35 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.modifiers + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.design.Color + +@Serializable +@ExperimentalIconsApi +class StrokeModifier( + val color: Color +): IconModifier { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as StrokeModifier + + return color == other.color + } + + override fun hashCode(): Int { + return color.hashCode() + } + + override fun toString(): String { + return "StrokeIconModifier(color=$color)" + } + +} + +@ExperimentalIconsApi +fun IconModifier.stroke(color: Color): IconModifier { + return this then StrokeModifier(color) +} \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/modifiers/SvgPatcherModifier.kt b/platform/icons-api/src/org/jetbrains/icons/modifiers/SvgPatcherModifier.kt new file mode 100644 index 0000000000000..12d683eaea6b4 --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/modifiers/SvgPatcherModifier.kt @@ -0,0 +1,44 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.modifiers + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.design.SvgPatcherDesigner +import org.jetbrains.icons.patchers.SvgPatcher + +@Serializable +@ExperimentalIconsApi +class SvgPatcherModifier( + val svgPatcher: SvgPatcher +): IconModifier { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as SvgPatcherModifier + + return svgPatcher == other.svgPatcher + } + + override fun hashCode(): Int { + return svgPatcher.hashCode() + } + + override fun toString(): String { + return "SvgPatcherModifier(svgPatcher=$svgPatcher)" + } + +} + +@ExperimentalIconsApi +fun IconModifier.patchSvg(svgPatcher: SvgPatcher): IconModifier { + return this then SvgPatcherModifier(svgPatcher) +} + +@ExperimentalIconsApi +fun IconModifier.patchSvg(svgPatcherBuilder: SvgPatcherDesigner.() -> Unit): IconModifier { + return this.patchSvg(svgPatcher(svgPatcherBuilder)) +} + +@ExperimentalIconsApi +fun svgPatcher(svgPatcherBuilder: SvgPatcherDesigner.() -> Unit): SvgPatcher = SvgPatcherDesigner().apply(svgPatcherBuilder).build() \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/modifiers/WidthIconModifier.kt b/platform/icons-api/src/org/jetbrains/icons/modifiers/WidthIconModifier.kt new file mode 100644 index 0000000000000..884ba8fe5e872 --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/modifiers/WidthIconModifier.kt @@ -0,0 +1,34 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.modifiers + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.design.IconUnit + +@Serializable +@ExperimentalIconsApi +class WidthIconModifier( + val width: IconUnit +): IconModifier { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as WidthIconModifier + + return width == other.width + } + + override fun hashCode(): Int { + return width.hashCode() + } + + override fun toString(): String { + return "WidthIconModifier(width=$width)" + } +} + +@ExperimentalIconsApi +fun IconModifier.width(width: IconUnit): IconModifier { + return this then WidthIconModifier(width) +} \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/patchers/SvgPatcher.kt b/platform/icons-api/src/org/jetbrains/icons/patchers/SvgPatcher.kt new file mode 100644 index 0000000000000..1ba8bc60e4759 --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/patchers/SvgPatcher.kt @@ -0,0 +1,123 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.patchers + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.ExperimentalIconsApi + +@ExperimentalIconsApi +@Serializable +public class SvgPatcher( + val operations: List, + val filteredOperations: List +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as SvgPatcher + + if (operations != other.operations) return false + if (filteredOperations != other.filteredOperations) return false + + return true + } + + override fun hashCode(): Int { + var result = operations.hashCode() + result = 31 * result + filteredOperations.hashCode() + return result + } + + override fun toString(): String { + return "SvgPatcher(operations=$operations, filteredOperations=$filteredOperations)" + } + +} + +@ExperimentalIconsApi +infix fun SvgPatcher?.combineWith(other: SvgPatcher?): SvgPatcher? { + if (this == null && other == null) return null + if (this == null) return other!! + if (other == null) return this + return SvgPatcher(operations + other.operations, filteredOperations + other.filteredOperations) +} + +@ExperimentalIconsApi +@Serializable +public class SvgPathFilteredOperations( + val path: String, + val operations: List +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as SvgPathFilteredOperations + + if (path != other.path) return false + if (operations != other.operations) return false + + return true + } + + override fun hashCode(): Int { + var result = path.hashCode() + result = 31 * result + operations.hashCode() + return result + } + + override fun toString(): String { + return "SvgPathFilteredOperations(path='$path', operations=$operations)" + } + +} + +@ExperimentalIconsApi +@Serializable +public class SvgPatchOperation( + val attributeName: String, + val value: String?, + val conditional: Boolean, + val negatedCondition: Boolean, + val expectedValue: String?, + val operation: Operation +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as SvgPatchOperation + + if (conditional != other.conditional) return false + if (negatedCondition != other.negatedCondition) return false + if (attributeName != other.attributeName) return false + if (value != other.value) return false + if (expectedValue != other.expectedValue) return false + if (operation != other.operation) return false + + return true + } + + override fun hashCode(): Int { + var result = conditional.hashCode() + result = 31 * result + negatedCondition.hashCode() + result = 31 * result + attributeName.hashCode() + result = 31 * result + (value.hashCode() ?: 0) + result = 31 * result + (expectedValue.hashCode() ?: 0) + result = 31 * result + operation.hashCode() + return result + } + + override fun toString(): String { + return "SvgPatchOperation(attributeName='$attributeName', value=$value, conditional=$conditional, negatedCondition=$negatedCondition, expectedValue=$expectedValue, operation=$operation)" + } + + @Serializable + enum class Operation { + Add, + Replace, + Remove, + Set + } + +} diff --git a/platform/icons-impl/.gitignore b/platform/icons-impl/.gitignore new file mode 100644 index 0000000000000..af272e31f1de3 --- /dev/null +++ b/platform/icons-impl/.gitignore @@ -0,0 +1,111 @@ +### macOS template +# General +.DS_Store +.AppleDouble +.LSOverride + +# Thumbnails +._* + +### Gradle template +.gradle +build/ + +### Terraform template +# Local .terraform directories +**/.terraform/* + +# .tfstate files +*.tfstate +*.tfstate.* + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +*.ipr +*.iws +.idea/* +out/ +local.properties + +# IDEA/Android Studio project settings ignore exceptions +!.idea/codeStyles/ +!.idea/copyright/ +!.idea/dataSources.xml +!.idea/detekt.xml +!.idea/encodings.xml +!.idea/externalDependencies.xml +!.idea/fileTemplates/ +!.idea/icon.svg +!.idea/icon.png +!.idea/icon_dark.png +!.idea/inspectionProfiles/ +!.idea/ktfmt.xml +!.idea/ktlint.xml +!.idea/ktlint-plugin.xml +!.idea/runConfigurations/ +!.idea/scopes/ +!.idea/vcs.xml + +### Kotlin template +# Compiled class file +*.class + +# Log file +*.log + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### Windows template +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +### Misc + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Ignore IJP temp folder +/.intellijPlatform + +# Ignore release patch generator output +/this-release.txt + +# Ignore Kotlin compiler sessions +/.kotlin +/buildSrc/.kotlin +/foundation/bin/* +/samples/showcase/bin/* +/new_release_notes.md diff --git a/platform/icons-impl/BUILD.bazel b/platform/icons-impl/BUILD.bazel new file mode 100644 index 0000000000000..0a781ef5209cc --- /dev/null +++ b/platform/icons-impl/BUILD.bazel @@ -0,0 +1,46 @@ +### auto-generated section `build intellij.platform.icons.impl` start +load("//build:compiler-options.bzl", "create_kotlinc_options") +load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") + +create_kotlinc_options( + name = "custom_icons-impl", + api_version = "2.2", + language_version = "2.2", + opt_in = [ + "org.jetbrains.icons.api.InternalIconsApi", + "org.jetbrains.icons.api.ExperimentalIconsApi", + "org.jetbrains.icons.ExperimentalIconsApi", + "org.jetbrains.icons.InternalIconsApi", + ], + x_x_language = [] +) + +resourcegroup( + name = "icons-impl_resources", + srcs = glob(["resources/**/*"]), + strip_prefix = "resources" +) + +jvm_library( + name = "icons-impl", + module_name = "intellij.platform.icons.impl", + visibility = ["//visibility:public"], + srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), + resources = [":icons-impl_resources"], + kotlinc_opts = ":custom_icons-impl", + deps = [ + "@lib//:kotlin-stdlib", + "//libraries/kotlinx/coroutines/core", + "//platform/icons-api", + "//platform/icons-api/rendering", + "//platform/icons-api/rendering/lowlevel", + "//libraries/kotlinx/serialization/core", + "//platform/icons-api/legacy-icon-support", + ], + exports = [ + "//platform/icons-api", + "//platform/icons-api/rendering", + "//platform/icons-api/rendering/lowlevel", + ] +) +### auto-generated section `build intellij.platform.icons.impl` end \ No newline at end of file diff --git a/platform/icons-impl/README.md b/platform/icons-impl/README.md new file mode 100644 index 0000000000000..bf3d86a460a24 --- /dev/null +++ b/platform/icons-impl/README.md @@ -0,0 +1,3 @@ +## Structure +- root - General implementation, reused +- intellij - IntelliJ specific implementations \ No newline at end of file diff --git a/platform/icons-impl/build.gradle.kts b/platform/icons-impl/build.gradle.kts new file mode 100644 index 0000000000000..69e56dc7d7560 --- /dev/null +++ b/platform/icons-impl/build.gradle.kts @@ -0,0 +1,27 @@ +// This file is used by Jewel gradle script, check community/platform/jewel + +plugins { + jewel +} + +sourceSets { + main { + kotlin { + setSrcDirs(listOf("src")) + } + } + + test { + kotlin { + setSrcDirs(listOf("test")) + } + } +} + +dependencies { + api(project(":jb-icons-api")) + api(project(":jb-icons-api-rendering")) + api(project(":jb-icons-api-rendering-lowlevel")) + api(libs.kotlinx.serialization.core) + api(libs.kotlinx.coroutines.core) +} diff --git a/platform/icons-impl/intellij.platform.icons.impl.iml b/platform/icons-impl/intellij.platform.icons.impl.iml new file mode 100644 index 0000000000000..797c270a0b6ad --- /dev/null +++ b/platform/icons-impl/intellij.platform.icons.impl.iml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + $KOTLIN_BUNDLED$/lib/kotlinx-serialization-compiler-plugin.jar + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/platform/icons-impl/intellij/BUILD.bazel b/platform/icons-impl/intellij/BUILD.bazel new file mode 100644 index 0000000000000..b01aef363d512 --- /dev/null +++ b/platform/icons-impl/intellij/BUILD.bazel @@ -0,0 +1,90 @@ +### auto-generated section `build intellij.platform.icons.impl.intellij` start +load("//build:compiler-options.bzl", "create_kotlinc_options") +load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") + +create_kotlinc_options( + name = "custom_intellij", + api_version = "2.2", + language_version = "2.2", + opt_in = [ + "org.jetbrains.icons.api.ExperimentalIconsApi", + "org.jetbrains.icons.api.InternalIconsApi", + "org.jetbrains.icons.ExperimentalIconsApi", + "org.jetbrains.icons.InternalIconsApi", + ], + x_x_language = [] +) + +resourcegroup( + name = "intellij_resources", + srcs = glob(["resources/**/*"]), + strip_prefix = "resources" +) + +jvm_library( + name = "intellij", + module_name = "intellij.platform.icons.impl.intellij", + visibility = ["//visibility:public"], + srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), + resources = [":intellij_resources"], + kotlinc_opts = ":custom_intellij", + deps = [ + "@lib//:kotlin-stdlib", + "//libraries/kotlinx/coroutines/core", + "//platform/icons-api", + "//platform/icons-api/rendering", + "//platform/icons-impl", + "//platform/util", + "//platform/util:util-ui", + "//platform/core-api:core", + "//platform/platform-impl/rpc", + "//fleet/util/core", + "@lib//:platform-icons-impl-intellij-jetbrains-kotlinx-serialization-core-jvm", + "//platform/core-impl", + "//platform/icons-api/legacy-icon-support", + "//platform/platform-impl:ide-impl", + "//platform/core-ui", + ] +) + +jvm_library( + name = "intellij_test_lib", + visibility = ["//visibility:public"], + srcs = glob(["test/**/*.kt", "test/**/*.java", "test/**/*.form"], allow_empty = True), + kotlinc_opts = ":custom_intellij", + associates = [":intellij"], + deps = [ + "@lib//:kotlin-stdlib", + "//libraries/kotlinx/coroutines/core", + "//platform/icons-api", + "//platform/icons-api/rendering", + "//platform/icons-impl", + "//platform/util", + "//platform/util:util-ui", + "//platform/core-api:core", + "//platform/platform-impl/rpc", + "//fleet/util/core", + "@lib//:platform-icons-impl-intellij-jetbrains-kotlinx-serialization-core-jvm", + "//platform/core-impl", + "//libraries/junit5", + "//platform/testFramework/junit5", + "//platform/testFramework/junit5:junit5_test_lib", + "//platform/icons-api/legacy-icon-support", + "//platform/platform-impl:ide-impl", + "//platform/core-ui", + "//libraries/kotlinx/serialization/json", + "@lib//:junit5", + "@lib//:junit5Jupiter", + "@lib//:assert_j", + ] +) +### auto-generated section `build intellij.platform.icons.impl.intellij` end + +### auto-generated section `test intellij.platform.icons.impl.intellij` start +load("@community//build:tests-options.bzl", "jps_test") + +jps_test( + name = "intellij_test", + runtime_deps = [":intellij_test_lib"] +) +### auto-generated section `test intellij.platform.icons.impl.intellij` end \ No newline at end of file diff --git a/platform/icons-impl/intellij/intellij.platform.icons.impl.intellij.iml b/platform/icons-impl/intellij/intellij.platform.icons.impl.intellij.iml new file mode 100644 index 0000000000000..af444173212c7 --- /dev/null +++ b/platform/icons-impl/intellij/intellij.platform.icons.impl.intellij.iml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + $KOTLIN_BUNDLED$/lib/kotlinx-serialization-compiler-plugin.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3565b6d4d789bf70683c45566944287fc1d8dc75c23d98bd87d01059cc76f2b3 + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/platform/icons-impl/intellij/resources/intellij.platform.icons.impl.intellij.xml b/platform/icons-impl/intellij/resources/intellij.platform.icons.impl.intellij.xml new file mode 100644 index 0000000000000..080b1e34279be --- /dev/null +++ b/platform/icons-impl/intellij/resources/intellij.platform.icons.impl.intellij.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt new file mode 100644 index 0000000000000..f69758ab4571f --- /dev/null +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt @@ -0,0 +1,54 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij + +import kotlinx.serialization.modules.SerializersModuleBuilder +import org.jetbrains.icons.DynamicIcon +import org.jetbrains.icons.Icon +import org.jetbrains.icons.design.IconDesigner +import org.jetbrains.icons.legacyIconSupport.SwingIconManager +import org.jetbrains.icons.impl.DefaultIconManager +import org.jetbrains.icons.impl.intellij.custom.CustomIconLayerRegistration +import org.jetbrains.icons.impl.intellij.custom.CustomImageResourceLoader +import org.jetbrains.icons.impl.intellij.custom.CustomLegacyIconSerializer +import org.jetbrains.icons.impl.intellij.design.IntelliJIconDesigner +import org.jetbrains.icons.impl.intellij.rendering.IntelliJIconRendererManager +import org.jetbrains.icons.impl.intellij.rendering.IntelliJImageResourceLoader +import org.jetbrains.icons.impl.intellij.rendering.IntelliJImageResourceLoaderSerializer +import org.jetbrains.icons.impl.intellij.rendering.IntelliJImageResourceProvider +import org.jetbrains.icons.impl.rendering.SwingIcon +import org.jetbrains.icons.rendering.IconRendererManager +import org.jetbrains.icons.rendering.ImageResourceLoader +import org.jetbrains.icons.rendering.ImageResourceProvider + +class IntelliJIconManager : DefaultIconManager(), SwingIconManager { + override fun icon(designer: IconDesigner.() -> Unit): Icon { + val ijIconDesigner = IntelliJIconDesigner() + ijIconDesigner.designer() + return ijIconDesigner.build() + } + + override fun dynamicIcon(icon: Icon): DynamicIcon { + return createDynamicIcon(icon) { dynamicIcon, content -> + // TODO Send updates over network (also listen for them) + } + } + + override fun toSwingIcon(icon: Icon): javax.swing.Icon { + return SwingIcon(icon) + } + + override fun SerializersModuleBuilder.buildCustomSerializers() { + CustomLegacyIconSerializer.registerSerializersTo(this) + CustomImageResourceLoader.registerSerializersTo(this) + CustomIconLayerRegistration.registerSerializersTo(this) + polymorphic(ImageResourceLoader::class, IntelliJImageResourceLoader::class, IntelliJImageResourceLoaderSerializer) + } + + companion object { + fun activate() { + org.jetbrains.icons.IconManager.activate(IntelliJIconManager()) + IconRendererManager.activate(IntelliJIconRendererManager()) + ImageResourceProvider.activate(IntelliJImageResourceProvider()) + } + } +} \ No newline at end of file diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomIconLayerRegistration.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomIconLayerRegistration.kt new file mode 100644 index 0000000000000..f1ff4fe37a8d3 --- /dev/null +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomIconLayerRegistration.kt @@ -0,0 +1,16 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij.custom + +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.icons.layers.IconLayer +import kotlin.reflect.KClass + +abstract class CustomIconLayerRegistration( + klass: KClass +): CustomSerializableRegistration(klass) { + @ApiStatus.Internal + companion object: CustomSerializableRegistration.Companion>( + IconLayer ::class, + "com.intellij.customIconLayer" + ) +} \ No newline at end of file diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomIconLayerRendererProvider.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomIconLayerRendererProvider.kt new file mode 100644 index 0000000000000..1141e0fb110cb --- /dev/null +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomIconLayerRendererProvider.kt @@ -0,0 +1,25 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij.custom + +import com.intellij.openapi.extensions.ExtensionPointName +import org.jetbrains.icons.impl.intellij.rendering.SwingIconLayerRenderer +import org.jetbrains.icons.layers.IconLayer +import org.jetbrains.icons.impl.rendering.layers.IconLayerRenderer +import org.jetbrains.icons.legacyIconSupport.SwingIconLayer +import org.jetbrains.icons.rendering.RenderingContext + +interface CustomIconLayerRendererProvider { + fun handles(layer: IconLayer): Boolean + fun createRenderer(layer: IconLayer, renderingContext: RenderingContext): IconLayerRenderer + + companion object { + fun createRendererFor(layer: IconLayer, renderingContext: RenderingContext): IconLayerRenderer? { + for (extension in EP_NAME.extensionList) { + if (extension.handles(layer)) return SwingIconLayerRenderer(layer as SwingIconLayer, renderingContext) + } + return null + } + + val EP_NAME: ExtensionPointName = ExtensionPointName("com.intellij.customIconLayerRendererProvider") + } +} \ No newline at end of file diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomImageResourceLoader.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomImageResourceLoader.kt new file mode 100644 index 0000000000000..86c6e195921a8 --- /dev/null +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomImageResourceLoader.kt @@ -0,0 +1,17 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij.custom + +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.icons.rendering.ImageResourceLoader +import javax.swing.Icon +import kotlin.reflect.KClass + +abstract class CustomImageResourceLoader( + klass: KClass +): CustomSerializableRegistration(klass) { + @ApiStatus.Internal + companion object: CustomSerializableRegistration.Companion>( + ImageResourceLoader::class, + "com.intellij.customImageResourceLoader" + ) +} \ No newline at end of file diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomLegacyIconSerializer.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomLegacyIconSerializer.kt new file mode 100644 index 0000000000000..1783576c9b455 --- /dev/null +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomLegacyIconSerializer.kt @@ -0,0 +1,16 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij.custom + +import org.jetbrains.annotations.ApiStatus +import javax.swing.Icon +import kotlin.reflect.KClass + +abstract class CustomLegacyIconSerializer( + klass: KClass +): CustomSerializableRegistration(klass) { + @ApiStatus.Internal + companion object: CustomSerializableRegistration.Companion>( + Icon::class, + "com.intellij.customLegacyIconSerializer" + ) +} diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomSerializableRegistration.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomSerializableRegistration.kt new file mode 100644 index 0000000000000..19eb0f81441a6 --- /dev/null +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomSerializableRegistration.kt @@ -0,0 +1,41 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij.custom + +import com.intellij.openapi.extensions.ExtensionPointName +import kotlinx.serialization.KSerializer +import kotlinx.serialization.modules.SerializersModuleBuilder +import kotlin.reflect.KClass + +abstract class CustomSerializableRegistration( + internal val klass: KClass +) { + abstract fun createSerializer(): KSerializer + + abstract class Companion>( + private val baseClass: KClass, + extensionName: String + ) { + fun registerSerializersTo(builder: SerializersModuleBuilder) { + EP_NAME.extensionList.forEach { instance -> + val serializer = instance.createSerializer() + @Suppress("UNCHECKED_CAST") + builder.addCustom(instance as CustomSerializableRegistration) + onRegistration(instance, serializer) + } + } + + private fun SerializersModuleBuilder.addCustom(instance: CustomSerializableRegistration) { + polymorphic( + baseClass, + instance.klass, + instance.createSerializer() + ) + } + + open fun onRegistration(ep: TEP, serializer: KSerializer<*>) { + // Do nothing in default implementation + } + + val EP_NAME: ExtensionPointName = ExtensionPointName(extensionName) + } +} \ No newline at end of file diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/design/IntelliJIconDesigner.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/design/IntelliJIconDesigner.kt new file mode 100644 index 0000000000000..2f956ab21ec83 --- /dev/null +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/design/IntelliJIconDesigner.kt @@ -0,0 +1,19 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij.design + +import com.intellij.ui.icons.findIconLoaderByPath +import org.jetbrains.icons.modifiers.IconModifier +import org.jetbrains.icons.impl.design.DefaultIconDesigner +import org.jetbrains.icons.impl.intellij.rendering.IntelliJImageResourceLoader +import org.jetbrains.icons.layers.IconLayer + +class IntelliJIconDesigner: DefaultIconDesigner() { + override fun image(path: String, classLoader: ClassLoader?, modifier: IconModifier) { + val loader = findIconLoaderByPath(path, classLoader ?: this.javaClass.classLoader) + image(IntelliJImageResourceLoader(loader), modifier) + } + + override fun createNestedDesigner(): DefaultIconDesigner { + return IntelliJIconDesigner() + } +} \ No newline at end of file diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/Convertors.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/Convertors.kt new file mode 100644 index 0000000000000..cf2f8c77d7330 --- /dev/null +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/Convertors.kt @@ -0,0 +1,108 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij.rendering + +import com.intellij.ui.svg.SvgAttributePatcher +import com.intellij.util.SVGLoader +import org.jetbrains.icons.design.BlendMode +import org.jetbrains.icons.filters.ColorFilter +import org.jetbrains.icons.filters.TintColorFilter +import org.jetbrains.icons.patchers.SvgPatchOperation +import org.jetbrains.icons.patchers.SvgPatcher +import org.jetbrains.icons.impl.rendering.toAwtColor +import java.awt.Color +import java.awt.image.RGBImageFilter + +internal fun ColorFilter.toAwtFilter(): RGBImageFilter { + return when (this) { + is TintColorFilter -> { + AwtColorFilter.fromColorAndBlendMode(color, blendMode) + } + } +} + +internal fun SvgPatcher.toIJPatcher(): SVGLoader.SvgElementColorPatcherProvider { + return ProxySvgPatcher(this) +} + +private class ProxySvgPatcher( + private val patcher: SvgPatcher +): SVGLoader.SvgElementColorPatcherProvider, SvgAttributePatcher { + override fun attributeForPath(path: String): SvgAttributePatcher = this + override fun digest(): LongArray { + return longArrayOf(patcher.hashCode().toLong()) + } + + override fun patchColors(attributes: MutableMap) { + // TODO Support filtered operations - not possible with current IJ svg loader + for (operation in patcher.operations) { + when (operation.operation) { + SvgPatchOperation.Operation.Add -> { + if (!attributes.containsKey(operation.attributeName)) { + attributes[operation.attributeName] = operation.value!! + } + } + SvgPatchOperation.Operation.Replace -> { + if (operation.conditional) { + val matches = attributes[operation.attributeName] == operation.expectedValue + if (matches == !operation.negatedCondition) { + attributes.replace(operation.attributeName, operation.value!!) + } + } else { + attributes.replace(operation.attributeName, operation.value!!) + } + } + SvgPatchOperation.Operation.Remove -> { + if (operation.conditional) { + val matches = attributes[operation.attributeName] == operation.expectedValue + if (matches == !operation.negatedCondition) { + attributes.remove(operation.attributeName) + } + } else { + attributes.remove(operation.attributeName) + } + } + SvgPatchOperation.Operation.Set -> attributes[operation.attributeName] = operation.value!! + } + } + } +} + +private class AwtColorFilter(val color: Color, val keepGray: Boolean, val keepBrightness: Boolean) : RGBImageFilter() { + private val base = Color.RGBtoHSB(color.red, color.green, color.blue, null) + + override fun filterRGB(x: Int, y: Int, rgba: Int): Int { + val r = rgba shr 16 and 0xff + val g = rgba shr 8 and 0xff + val b = rgba and 0xff + val hsb = FloatArray(3) + Color.RGBtoHSB(r, g, b, hsb) + val rgb = Color.HSBtoRGB(base[0], + base[1] * if (keepGray) hsb[1] else 1.0f, + base[2] * if (keepBrightness) hsb[2] else 1.0f) + return rgba and -0x1000000 or (rgb and 0xffffff) + } + + companion object { + fun fromColorAndBlendMode(color: org.jetbrains.icons.design.Color, blendMode: BlendMode): AwtColorFilter { + var keepGray = true + var keepBrightness = true + when (blendMode) { + BlendMode.Hue -> { + keepGray = false + keepBrightness = false + } + BlendMode.Saturation -> { + keepGray = false + } + else -> { + // Do nothing + } + } + return AwtColorFilter( + color.toAwtColor(), + keepGray, + keepBrightness + ) + } + } +} \ No newline at end of file diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IconUpdateService.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IconUpdateService.kt new file mode 100644 index 0000000000000..751b883b86750 --- /dev/null +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IconUpdateService.kt @@ -0,0 +1,30 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij.rendering + +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.launch +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.icons.InternalIconsApi + +@Service(Service.Level.APP) +@InternalIconsApi +class IconUpdateService(val scope: CoroutineScope) { + @ApiStatus.Experimental + fun scheduleDelayedUpdate(delay: Long, updateId: Int, flow: MutableSharedFlow, updateCallback: (Int) -> Unit, rateLimiter: () -> Boolean = { false }) { + scope.launch { + delay(delay) + if (rateLimiter()) return@launch + flow.emit(updateId) + updateCallback(updateId) + } + } + + companion object { + @JvmStatic + fun getInstance(): IconUpdateService = service() + } +} \ No newline at end of file diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJIconRendererManager.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJIconRendererManager.kt new file mode 100644 index 0000000000000..e066e7c6d4023 --- /dev/null +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJIconRendererManager.kt @@ -0,0 +1,52 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij.rendering + +import com.intellij.util.ui.StartupUiUtil +import kotlinx.coroutines.CoroutineScope +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.InternalIconsApi +import org.jetbrains.icons.impl.intellij.custom.CustomIconLayerRendererProvider +import org.jetbrains.icons.rendering.ImageModifiers +import org.jetbrains.icons.rendering.MutableIconUpdateFlow +import org.jetbrains.icons.rendering.RenderingContext +import org.jetbrains.icons.impl.rendering.CoroutineBasedMutableIconUpdateFlow +import org.jetbrains.icons.impl.rendering.DefaultIconRendererManager +import org.jetbrains.icons.layers.IconLayer +import org.jetbrains.icons.impl.rendering.DefaultImageModifiers +import org.jetbrains.icons.impl.rendering.layers.IconLayerRenderer + +@Suppress("UNCHECKED_CAST") +@OptIn(InternalIconsApi::class, ExperimentalIconsApi::class) +class IntelliJIconRendererManager: DefaultIconRendererManager() { + override fun createRenderer(layer: IconLayer, renderingContext: RenderingContext): IconLayerRenderer { + val defaultRenderer = createRendererOrNull(layer, renderingContext) + if (defaultRenderer != null) return defaultRenderer + + return CustomIconLayerRendererProvider.createRendererFor(layer, renderingContext) + ?: error("No renderer found for Icon Layer type: $layer\nMake sure that the corresponding renderer is properly registered.") + } + + override fun createUpdateFlow(scope: CoroutineScope?, updateCallback: (Int) -> Unit): MutableIconUpdateFlow { + if (scope != null) { + return CoroutineBasedMutableIconUpdateFlow(scope, updateCallback) + } else { + return IntelliJMutableIconUpdateFlowImpl(updateCallback) + } + } + + override fun createRenderingContext( + updateFlow: MutableIconUpdateFlow, + defaultImageModifiers: ImageModifiers?, + ): RenderingContext { + val knownModifiers = defaultImageModifiers as? DefaultImageModifiers + return RenderingContext( + updateFlow, + DefaultImageModifiers( + defaultImageModifiers?.colorFilter, + defaultImageModifiers?.svgPatcher, + StartupUiUtil.isDarkTheme, + knownModifiers?.stroke + ) + ) + } +} \ No newline at end of file diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJImageResourceLoader.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJImageResourceLoader.kt new file mode 100644 index 0000000000000..9901f76d58a9c --- /dev/null +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJImageResourceLoader.kt @@ -0,0 +1,85 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij.rendering + +import com.intellij.ui.icons.ImageDataLoader +import com.intellij.ui.icons.ImageDataLoaderDescriptor +import com.intellij.ui.icons.LoadIconParameters +import com.intellij.ui.scale.ScaleContext +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import org.jetbrains.icons.impl.rendering.DefaultImageModifiers +import org.jetbrains.icons.modifiers.svgPatcher +import org.jetbrains.icons.patchers.combineWith +import org.jetbrains.icons.rendering.ImageModifiers +import org.jetbrains.icons.rendering.ImageResourceLoader +import java.awt.Image +import java.awt.image.ImageFilter + +abstract class BaseIntelliJImageResourceLoader: SwingImageResourceLoader { + protected fun generateLoadIconParameters(imageModifiers: ImageModifiers?): LoadIconParameters { + val filters = mutableListOf() + val colorFilter = imageModifiers?.colorFilter + if (colorFilter != null) { + filters.add(colorFilter.toAwtFilter()) + } + val knownModifiers = imageModifiers as? DefaultImageModifiers + val strokePatcher = knownModifiers?.stroke?.let { stroke -> + svgPatcher { + replace("fill", "transparent") + add("stroke", stroke.toHex()) + } + } + val colorPatcher = (imageModifiers?.svgPatcher combineWith strokePatcher)?.toIJPatcher() + return LoadIconParameters( + filters = filters, + isDark = knownModifiers?.isDark ?: false, + colorPatcher = colorPatcher, + isStroke = knownModifiers?.stroke != null + ) + } +} + +@Serializable(with = IntelliJImageResourceLoaderSerializer::class) +class IntelliJImageResourceLoader( + val dataLoader: ImageDataLoader +): BaseIntelliJImageResourceLoader() { + override fun getExpectedDimensions(): Pair { + val img = loadImage(ScaleContext.create()) ?: return 0 to 0 + return img.getWidth(null) to img.getHeight(null) + } + + override fun loadImage(scale: ScaleContext, imageModifiers: ImageModifiers?): Image? { + return dataLoader.loadImage( + parameters = generateLoadIconParameters(imageModifiers), + scaleContext = scale + ) + } +} + +interface SwingImageResourceLoader: ImageResourceLoader { + fun getExpectedDimensions(): Pair + fun loadImage(scale: ScaleContext, imageModifiers: ImageModifiers? = null): Image? +} + +object IntelliJImageResourceLoaderSerializer: KSerializer { + private val actualSerializer = IconLoaderDescriptorHolder.serializer() + + override val descriptor: SerialDescriptor = actualSerializer.descriptor + + override fun serialize(encoder: Encoder, value: IntelliJImageResourceLoader) { + actualSerializer.serialize(encoder, IconLoaderDescriptorHolder(value.dataLoader.serializeToByteArray())) + } + + override fun deserialize(decoder: Decoder): IntelliJImageResourceLoader { + val descriptor = actualSerializer.deserialize(decoder).descriptor + return IntelliJImageResourceLoader(descriptor?.createIcon() ?: error("Unable to restore data loader from descriptor")) + } +} + +@Serializable +class IconLoaderDescriptorHolder( + val descriptor: ImageDataLoaderDescriptor? +) \ No newline at end of file diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJImageResourceProvider.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJImageResourceProvider.kt new file mode 100644 index 0000000000000..8d9acf24b6fc3 --- /dev/null +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJImageResourceProvider.kt @@ -0,0 +1,89 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij.rendering + +import com.intellij.openapi.util.IconLoader +import com.intellij.openapi.util.IconLoader.filterIcon +import com.intellij.ui.icons.CachedImageIcon +import com.intellij.ui.icons.RgbImageFilterSupplier +import com.intellij.ui.scale.ScaleContext +import com.intellij.ui.scale.ScaleType +import com.intellij.util.JBHiDPIScaledImage +import org.jetbrains.icons.rendering.BitmapImageResource +import org.jetbrains.icons.rendering.Bounds +import org.jetbrains.icons.rendering.EmptyBitmapImageResource +import org.jetbrains.icons.rendering.ImageModifiers +import org.jetbrains.icons.rendering.ImageResource +import org.jetbrains.icons.rendering.ImageResourceLoader +import org.jetbrains.icons.rendering.ImageScale +import org.jetbrains.icons.rendering.RescalableImageResource +import org.jetbrains.icons.impl.rendering.AwtImageResource +import org.jetbrains.icons.impl.rendering.CachedGPUImageResourceHolder +import org.jetbrains.icons.impl.rendering.DefaultImageResourceProvider +import java.awt.Image +import java.awt.image.RGBImageFilter +import javax.swing.Icon + +class IntelliJImageResourceProvider: DefaultImageResourceProvider() { + override fun loadImage(loader: ImageResourceLoader, imageModifiers: ImageModifiers?): ImageResource { + if (loader is SwingImageResourceLoader) { + return fromSwingLoader(loader, imageModifiers) + } else error("Unsupported loader: $loader") + } + + override fun fromSwingIcon(icon: Icon, imageModifiers: ImageModifiers?): ImageResource { + return IntelliJImageResource(LegacyIconImageResourceLoader(icon), imageModifiers) + } + + private fun fromSwingLoader(loader: SwingImageResourceLoader, imageModifiers: ImageModifiers? = null): ImageResource = + IntelliJImageResource(loader, imageModifiers) +} + +private class LegacyIconImageResourceLoader( + private val backingLegacyIcon: Icon +): BaseIntelliJImageResourceLoader() { + override fun getExpectedDimensions(): Pair { + return backingLegacyIcon.iconWidth to backingLegacyIcon.iconHeight + } + + override fun loadImage(scale: ScaleContext, imageModifiers: ImageModifiers?): Image? { + val params = generateLoadIconParameters(imageModifiers) + val svgPatcher = params.colorPatcher + val icon = if (backingLegacyIcon is CachedImageIcon && svgPatcher != null) { + backingLegacyIcon.createWithPatcher(svgPatcher, isDark = params.isDark, useStroke = params.isStroke) + } else backingLegacyIcon + val filtered = if (params.filters.isNotEmpty()) { + filterIcon(icon = icon, filterSupplier = object : RgbImageFilterSupplier { + override fun getFilter() = params.filters.first() as RGBImageFilter + }) + } else icon + return IconLoader.toImage(filtered, scale) + } +} + +internal class IntelliJImageResource( + private val loader: SwingImageResourceLoader, + private val imageModifiers: ImageModifiers? = null +): RescalableImageResource, CachedGPUImageResourceHolder() { + + override fun scale(scale: ImageScale): BitmapImageResource { + val (width, height) = loader.getExpectedDimensions() + val objScale = scale.calculateScalingFactorByOriginalDimensions(width, height) + val scaleContext = ScaleContext.of(arrayOf( + ScaleType.OBJ_SCALE.of(objScale), + ScaleType.USR_SCALE.of(1.0), + ScaleType.SYS_SCALE.of(1.0), + )) + val rawImage = loader.loadImage(scaleContext, imageModifiers) ?: return EmptyBitmapImageResource + val awtImage = (rawImage as? JBHiDPIScaledImage)?.delegate ?: rawImage + return AwtImageResource(awtImage) + } + + override fun calculateExpectedDimensions(scale: ImageScale): Bounds { + val (width, height) = loader.getExpectedDimensions() + val ijScale = scale.calculateScalingFactorByOriginalDimensions(width, height) + return Bounds(0, 0, width = (width * ijScale).toInt(), height = (height * ijScale).toInt()) + } + + override val width: Int = loader.getExpectedDimensions().first + override val height: Int = loader.getExpectedDimensions().second +} \ No newline at end of file diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJMutableIconUpdateFlowImpl.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJMutableIconUpdateFlowImpl.kt new file mode 100644 index 0000000000000..bf5d3d5cabce0 --- /dev/null +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJMutableIconUpdateFlowImpl.kt @@ -0,0 +1,24 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij.rendering + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.launch +import org.jetbrains.icons.Icon +import org.jetbrains.icons.impl.rendering.MutableIconUpdateFlowBase + +internal class IntelliJMutableIconUpdateFlowImpl( + updateCallback: (Int) -> Unit +) : MutableIconUpdateFlowBase(updateCallback) { + override fun MutableSharedFlow.emitDelayed(delay: Long, value: Int) { + IconUpdateService.getInstance().scheduleDelayedUpdate(delay, value, this@emitDelayed, updateCallback) { + handleRateLimiting() + } + } + + override fun collectDynamic(flow: Flow, handler: (Icon) -> Unit) { + IconUpdateService.getInstance().scope.launch { + flow.collect { handler(it) } + } + } +} \ No newline at end of file diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRegistration.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRegistration.kt new file mode 100644 index 0000000000000..0568cdd87987f --- /dev/null +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRegistration.kt @@ -0,0 +1,12 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij.rendering + +import kotlinx.serialization.KSerializer +import org.jetbrains.icons.impl.intellij.custom.CustomIconLayerRegistration +import org.jetbrains.icons.legacyIconSupport.SwingIconLayer + +internal class SwingIconLayerRegistration: CustomIconLayerRegistration(SwingIconLayer::class) { + override fun createSerializer(): KSerializer { + return SwingIconLayer.serializer() + } +} diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRenderer.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRenderer.kt new file mode 100644 index 0000000000000..b9201c06475bf --- /dev/null +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRenderer.kt @@ -0,0 +1,73 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij.rendering + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.ui.DeferredIcon +import com.intellij.ui.DeferredIconListener +import com.intellij.util.messages.impl.subscribeAsFlow +import kotlinx.coroutines.launch +import org.jetbrains.icons.impl.intellij.custom.CustomIconLayerRendererProvider +import org.jetbrains.icons.impl.rendering.DefaultImageResourceProvider +import org.jetbrains.icons.rendering.Dimensions +import org.jetbrains.icons.rendering.ImageResource +import org.jetbrains.icons.rendering.ImageResourceProvider +import org.jetbrains.icons.impl.rendering.layers.BaseImageIconLayerRenderer +import org.jetbrains.icons.impl.rendering.layers.IconLayerRenderer +import org.jetbrains.icons.rendering.RenderingContext +import org.jetbrains.icons.rendering.ScalingContext +import org.jetbrains.icons.impl.rendering.layers.applyTo +import org.jetbrains.icons.impl.rendering.layers.generateImageModifiers +import org.jetbrains.icons.layers.IconLayer +import org.jetbrains.icons.legacyIconSupport.SwingIconLayer + +class SwingIconLayerRendererProvider: CustomIconLayerRendererProvider { + override fun handles(layer: IconLayer): Boolean { + return layer is SwingIconLayer + } + + override fun createRenderer(layer: IconLayer, renderingContext: RenderingContext): IconLayerRenderer { + val swingLayer = layer as SwingIconLayer + val renderer = SwingIconLayerRenderer( + swingLayer, + renderingContext + ) + renderer.launchEventBridge() + return renderer + } +} + +class SwingIconLayerRenderer( + override val layer: SwingIconLayer, + override val renderingContext: RenderingContext +): BaseImageIconLayerRenderer() { + override var image: ImageResource = createImageResource() + + fun launchEventBridge() { + val icon = layer.legacyIcon + if (layer.legacyIcon is DeferredIcon) { + val flow = ApplicationManager.getApplication().messageBus.subscribeAsFlow(DeferredIconListener.TOPIC) { + object : DeferredIconListener { + override fun evaluated(deferred: DeferredIcon, result: javax.swing.Icon) { + if (deferred != icon) return + trySend(result) + } + } + } + IconUpdateService.getInstance().scope.launch { + flow.collect { + image = createImageResource() + } + } + } + } + + private fun createImageResource(): ImageResource { + val provider = ImageResourceProvider.getInstance() as? DefaultImageResourceProvider + if (provider == null) error("Swing Icon fallback is only supported with DefaultImageResourceProvider") + return provider.fromSwingIcon(layer.legacyIcon, layer.generateImageModifiers(renderingContext)) + } + + override fun calculateExpectedDimensions(scaling: ScalingContext): Dimensions { + return Dimensions(scaling.applyTo(image.width) ?: 16, scaling.applyTo(image.height) ?: 16) + } +} \ No newline at end of file diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/serializers/CachedImageIconSerializer.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/serializers/CachedImageIconSerializer.kt new file mode 100644 index 0000000000000..8481bd669758b --- /dev/null +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/serializers/CachedImageIconSerializer.kt @@ -0,0 +1,35 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij.serializers + +import com.intellij.ui.icons.CachedImageIcon +import com.intellij.ui.icons.decodeCachedImageIconFromByteArray +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import org.jetbrains.icons.impl.intellij.custom.CustomLegacyIconSerializer + +class CustomCachedImageIconSerializer: CustomLegacyIconSerializer(CachedImageIcon::class) { + override fun createSerializer(): KSerializer = CachedImageIconSerializer +} + +object CachedImageIconSerializer: KSerializer { + private val actualSerializer = SerializedIconDataHolder.serializer() + + override val descriptor: SerialDescriptor = actualSerializer.descriptor + + override fun serialize(encoder: Encoder, value: CachedImageIcon) { + actualSerializer.serialize(encoder, SerializedIconDataHolder(value.encodeToByteArray())) + } + + override fun deserialize(decoder: Decoder): CachedImageIcon { + val byteArray = actualSerializer.deserialize(decoder).data + return decodeCachedImageIconFromByteArray(byteArray) as? CachedImageIcon ?: error("Unable to restore CachedImageIcon from byte array") + } +} + +@Serializable +class SerializedIconDataHolder( + val data: ByteArray +) \ No newline at end of file diff --git a/platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DefaultIconSerializationTest.kt b/platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DefaultIconSerializationTest.kt new file mode 100644 index 0000000000000..5e85c311eb5a8 --- /dev/null +++ b/platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DefaultIconSerializationTest.kt @@ -0,0 +1,34 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij + +import com.intellij.ide.rpc.deserializeFromRpc +import com.intellij.ide.rpc.serializeToRpc +import com.intellij.openapi.application.Application +import com.intellij.openapi.application.ApplicationManager +import com.intellij.testFramework.junit5.TestApplication +import org.jetbrains.icons.Icon +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +@TestApplication +class DefaultIconSerializationTest { + @Test + fun `should serialize and deserialize default icon`() { + val iconManager = IntelliJIconManager() + val icon2 = iconManager.icon { + image("test.svg") + } + val icon1 = iconManager.icon { + row { + column { + image("test.png", IntelliJIconManager::class.java.classLoader) + image("test2.svg", IntelliJIconManager::class.java.classLoader) + } + icon(icon2) + } + } + val serialized = serializeToRpc(icon1) + val deserialized = deserializeFromRpc(serialized, Icon::class) + assertTrue(deserialized == icon1) + } +} \ No newline at end of file diff --git a/platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DynamicIconTest.kt b/platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DynamicIconTest.kt new file mode 100644 index 0000000000000..0b435c86b4e6b --- /dev/null +++ b/platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DynamicIconTest.kt @@ -0,0 +1,58 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij + +import com.intellij.testFramework.junit5.TestApplication +import com.intellij.testFramework.junit5.fixture.TestFixtures +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.json.Json +import org.assertj.core.api.Assertions.assertThat +import org.jetbrains.icons.DynamicIcon +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.Icon +import org.jetbrains.icons.IconManager +import org.jetbrains.icons.dynamicIcon +import org.jetbrains.icons.imageIcon +import org.jetbrains.icons.rendering.IconRendererManager +import org.jetbrains.icons.rendering.RenderingContext +import org.jetbrains.icons.rendering.createRenderer +import org.junit.jupiter.api.Test + +@OptIn(DelicateCoroutinesApi::class) +@TestApplication +class DynamicIconTest { + @OptIn(ExperimentalIconsApi::class) + + @Test + fun `should be properly updated after serialization and deserialization`() { + IntelliJIconManager.activate() + runBlocking { + val binary = imageIcon("fileTypes/binaryData.svg") + val css = imageIcon("fileTypes/css.svg") + val dynamicIcon = dynamicIcon(binary) + val json = Json { + serializersModule = IconManager.getInstance().getSerializersModule() + } + val serialized = json.encodeToString(dynamicIcon) + val deserialized = json.decodeFromString(serialized) + var lastChangeId = 0 + val flow = IconRendererManager.createUpdateFlow(this) { changeId -> + lastChangeId = changeId + } + assertThat(deserialized).isInstanceOf(DynamicIcon::class.java) + val deserializedDynamicIcon = deserialized as DynamicIcon + val icon = deserializedDynamicIcon.createRenderer(RenderingContext(flow)) + assertThat(deserializedDynamicIcon.getCurrentIcon()).isEqualTo(binary) + val asyncTask = GlobalScope.launch { + delay(1000) + dynamicIcon.swap(css) + } + asyncTask.join() + assertThat(deserializedDynamicIcon.getCurrentIcon()).isEqualTo(css) + assertThat(lastChangeId).isGreaterThan(0) + } + } +} \ No newline at end of file diff --git a/platform/icons-impl/resources/intellij.platform.icons.impl.xml b/platform/icons-impl/resources/intellij.platform.icons.impl.xml new file mode 100644 index 0000000000000..12bb3204e3594 --- /dev/null +++ b/platform/icons-impl/resources/intellij.platform.icons.impl.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultDynamicIcon.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultDynamicIcon.kt new file mode 100644 index 0000000000000..2297db2f69a22 --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultDynamicIcon.kt @@ -0,0 +1,57 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.serialization.Serializable +import org.jetbrains.icons.DynamicIcon +import org.jetbrains.icons.Icon + +@Serializable +open class DefaultDynamicIcon( + internal val id: String, + internal var currentIcon: DefaultLayeredIcon, + @kotlinx.serialization.Transient + private val updateListener: ((DynamicIcon, Icon) -> Unit)? = null +): DynamicIcon { + @kotlinx.serialization.Transient + private val updateFlow = MutableSharedFlow() + + override fun getCurrentIcon(): Icon { + return currentIcon + } + + override suspend fun swap(icon: Icon) { + if (icon !is DefaultLayeredIcon) error("Unsupported icon type: $icon") + currentIcon = icon + updateListener?.invoke(this, icon) + updateFlow.emit(icon) + } + + override fun getFlow(): Flow { + return updateFlow + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as DefaultDynamicIcon + + if (id != other.id) return false + if (currentIcon != other.currentIcon) return false + + return true + } + + override fun hashCode(): Int { + var result = id.hashCode() + result = 31 * result + currentIcon.hashCode() + return result + } + + override fun toString(): String { + return "IntelliJDynamicIcon(id='$id', currentIcon=$currentIcon)" + } + +} diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultIconManager.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultIconManager.kt new file mode 100644 index 0000000000000..dfcde427635f9 --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultIconManager.kt @@ -0,0 +1,91 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.descriptors.serialDescriptor +import kotlinx.serialization.encoding.CompositeDecoder +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.decodeStructure +import kotlinx.serialization.encoding.encodeStructure +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.SerializersModuleBuilder +import org.jetbrains.icons.DynamicIcon +import org.jetbrains.icons.Icon +import org.jetbrains.icons.IconManager +import org.jetbrains.icons.impl.layers.AnimatedIconLayer +import org.jetbrains.icons.impl.layers.IconIconLayer +import org.jetbrains.icons.layers.IconLayer +import org.jetbrains.icons.impl.layers.ImageIconLayer +import org.jetbrains.icons.impl.layers.LayoutIconLayer +import org.jetbrains.icons.impl.layers.ShapeIconLayer +import org.jetbrains.icons.modifiers.IconModifier + +abstract class DefaultIconManager: IconManager { + override fun dynamicIcon(icon: Icon): DynamicIcon { + return DefaultDynamicIcon("dynamic_" + dynamicIconNextId++, icon as DefaultLayeredIcon) + } + + protected fun createDynamicIcon(icon: Icon, updateListener: (DynamicIcon, Icon) -> Unit): DynamicIcon { + return DefaultDynamicIcon("dynamic?" + dynamicIconNextId++, icon as DefaultLayeredIcon, updateListener) + } + + protected open fun SerializersModuleBuilder.buildCustomSerializers() { + // Register nothing in default implementation + } + + override fun getSerializersModule(): SerializersModule { + return SerializersModule { + polymorphic(Icon::class, DefaultLayeredIcon::class, DefaultLayeredIcon.serializer()) + polymorphic(Icon::class, DefaultDynamicIcon::class, DefaultDynamicIcon.serializer()) + polymorphic(DynamicIcon::class, DefaultDynamicIcon::class, DefaultDynamicIcon.serializer()) + polymorphic( + IconModifier::class, + IconModifier.Companion::class, + IconModifierConstSerializer + ) + + iconLayer(AnimatedIconLayer::class) + iconLayer(IconIconLayer::class) + iconLayer(ImageIconLayer::class) + iconLayer(LayoutIconLayer::class) + iconLayer(ShapeIconLayer::class) + + buildCustomSerializers() + } + } + + private var dynamicIconNextId = 0 +} + +private object IconModifierConstSerializer : KSerializer { + override val descriptor: SerialDescriptor = + buildClassSerialDescriptor("IconModifier.Companion") { + element("isEmpty", serialDescriptor()) + } + + override fun serialize(encoder: Encoder, value: IconModifier.Companion) { + encoder.encodeStructure(descriptor) { + encodeBooleanElement(descriptor, 0, true) + } + } + + override fun deserialize(decoder: Decoder): IconModifier.Companion { + var v: Boolean? = null + decoder.decodeStructure(descriptor) { + while (true) { + when (val index = decodeElementIndex(descriptor)) { + CompositeDecoder.DECODE_DONE -> break + 0 -> v = decodeBooleanElement(descriptor, 0) + else -> error("Unexpected element index: $index") + } + } + } + require(v == true) { "Unexpected value: '$v'" } + return IconModifier + } +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultLayeredIcon.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultLayeredIcon.kt new file mode 100644 index 0000000000000..8e747ba3baf37 --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultLayeredIcon.kt @@ -0,0 +1,29 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.Icon +import org.jetbrains.icons.layers.IconLayer + +@Serializable +class DefaultLayeredIcon( + val layers: List +): Icon { + override fun toString(): String { + return "DefaultIcon(layers=$layers)" + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as DefaultLayeredIcon + + return layers == other.layers + } + + override fun hashCode(): Int { + return layers.hashCode() + } +} + diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/SerializersBuilder.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/SerializersBuilder.kt new file mode 100644 index 0000000000000..8905b5cecda49 --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/SerializersBuilder.kt @@ -0,0 +1,14 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl + +import kotlinx.serialization.InternalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.modules.SerializersModuleBuilder +import kotlinx.serialization.serializer +import org.jetbrains.icons.layers.IconLayer +import kotlin.reflect.KClass + +@OptIn(InternalSerializationApi::class) +fun SerializersModuleBuilder.iconLayer(klass: KClass, serializer: KSerializer? = null) { + polymorphic(IconLayer::class, klass, serializer ?: klass.serializer()) +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/design/DefaultIconAnimationDesigner.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/design/DefaultIconAnimationDesigner.kt new file mode 100644 index 0000000000000..07a66901f37c6 --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/design/DefaultIconAnimationDesigner.kt @@ -0,0 +1,22 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.design + +import org.jetbrains.icons.design.IconAnimationDesigner +import org.jetbrains.icons.design.IconDesigner +import org.jetbrains.icons.impl.rendering.IconAnimationFrame + +class DefaultIconAnimationDesigner( + val rootDesigner: DefaultIconDesigner +): IconAnimationDesigner { + private val frames = mutableListOf() + + override fun frame(duration: Long, builder: IconDesigner.() -> Unit) { + val designer = rootDesigner.createNestedDesigner() + designer.builder() + frames.add(IconAnimationFrame(designer.buildLayers(), duration)) + } + + fun build(): List { + return frames + } +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/design/DefaultIconDesigner.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/design/DefaultIconDesigner.kt new file mode 100644 index 0000000000000..e4dff9baedec2 --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/design/DefaultIconDesigner.kt @@ -0,0 +1,66 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.design + +import org.jetbrains.icons.Icon +import org.jetbrains.icons.design.Shape +import org.jetbrains.icons.design.Color +import org.jetbrains.icons.design.IconAnimationDesigner +import org.jetbrains.icons.design.IconDesigner +import org.jetbrains.icons.design.IconUnit +import org.jetbrains.icons.modifiers.IconModifier +import org.jetbrains.icons.impl.DefaultLayeredIcon +import org.jetbrains.icons.impl.layers.AnimatedIconLayer +import org.jetbrains.icons.impl.layers.IconIconLayer +import org.jetbrains.icons.layers.IconLayer +import org.jetbrains.icons.impl.layers.ImageIconLayer +import org.jetbrains.icons.impl.layers.LayoutIconLayer +import org.jetbrains.icons.rendering.ImageResourceLoader +import org.jetbrains.icons.impl.layers.ShapeIconLayer + +abstract class DefaultIconDesigner: IconDesigner { + private val layers = mutableListOf() + + protected fun image(loader: ImageResourceLoader, modifier: IconModifier) { + layers.add(ImageIconLayer(loader, modifier)) + } + + override fun icon(icon: Icon, modifier: IconModifier) { + layers.add(IconIconLayer(icon, modifier)) + } + + override fun custom(iconLayer: IconLayer) { + layers.add(iconLayer) + } + + override fun row(spacing: IconUnit, modifier: IconModifier, builder: IconDesigner.() -> Unit) { + layout(LayoutIconLayer.LayoutDirection.Row, spacing, modifier, builder) + } + + override fun column(spacing: IconUnit, modifier: IconModifier, builder: IconDesigner.() -> Unit) { + layout(LayoutIconLayer.LayoutDirection.Column, spacing, modifier, builder) + } + + private fun layout(direction: LayoutIconLayer.LayoutDirection, spacing: IconUnit, modifier: IconModifier, builder: IconDesigner.() -> Unit) { + val nestedIconDesigner = createNestedDesigner() + nestedIconDesigner.builder() + layers.add(LayoutIconLayer(nestedIconDesigner.buildLayers(), direction, spacing, modifier)) + } + + override fun animation(modifier: IconModifier, builder: IconAnimationDesigner.() -> Unit) { + val designer = DefaultIconAnimationDesigner(this) + designer.builder() + layers.add(AnimatedIconLayer(designer.build(), modifier)) + } + + override fun shape(color: Color, shape: Shape, modifier: IconModifier) { + layers.add(ShapeIconLayer(color, shape, modifier)) + } + + abstract fun createNestedDesigner(): DefaultIconDesigner + + fun build(): DefaultLayeredIcon { + return DefaultLayeredIcon(buildLayers()) + } + + fun buildLayers(): List = layers.toList() +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/layers/AnimatedIconLayer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/AnimatedIconLayer.kt new file mode 100644 index 0000000000000..bf1cd02eb93c2 --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/AnimatedIconLayer.kt @@ -0,0 +1,35 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.layers + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.modifiers.IconModifier +import org.jetbrains.icons.impl.rendering.IconAnimationFrame +import org.jetbrains.icons.layers.IconLayer + +@Serializable +class AnimatedIconLayer( + val frames: List, + override val modifier: IconModifier +): IconLayer { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as AnimatedIconLayer + + if (frames != other.frames) return false + if (modifier != other.modifier) return false + + return true + } + + override fun hashCode(): Int { + var result = frames.hashCode() + result = 31 * result + modifier.hashCode() + return result + } + + override fun toString(): String { + return "AnimatedIconLayer(frames=$frames, modifier=$modifier)" + } +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/layers/IconIconLayer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/IconIconLayer.kt new file mode 100644 index 0000000000000..cf4245faf8c6e --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/IconIconLayer.kt @@ -0,0 +1,35 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.layers + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.Icon +import org.jetbrains.icons.layers.IconLayer +import org.jetbrains.icons.modifiers.IconModifier + +@Serializable +class IconIconLayer( + val icon: Icon, + override val modifier: IconModifier +): IconLayer { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as IconIconLayer + + if (icon != other.icon) return false + if (modifier != other.modifier) return false + + return true + } + + override fun hashCode(): Int { + var result = icon.hashCode() + result = 31 * result + modifier.hashCode() + return result + } + + override fun toString(): String { + return "IconIconLayer(icon=$icon, modifier=$modifier)" + } +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/layers/IconLayerConstraints.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/IconLayerConstraints.kt new file mode 100644 index 0000000000000..9d9be222cc778 --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/IconLayerConstraints.kt @@ -0,0 +1,44 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.layers + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.design.IconAlign +import org.jetbrains.icons.design.IconMargin +import org.jetbrains.icons.design.IconUnit + +@Serializable +class IconLayerConstraints( + val align: IconAlign, + val width: IconUnit, + val height: IconUnit, + val margin: IconMargin, + val alpha: Float = 1.0f +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as IconLayerConstraints + + if (align != other.align) return false + if (width != other.width) return false + if (height != other.height) return false + if (margin != other.margin) return false + if (alpha != other.alpha) return false + + return true + } + + override fun hashCode(): Int { + var result = align.hashCode() + result = 31 * result + width.hashCode() + result = 31 * result + height.hashCode() + result = 31 * result + margin.hashCode() + result = 31 * result + alpha.hashCode() + return result + } + + override fun toString(): String { + return "IconLayerConstraints(align=$align, width=$width, height=$height, margin=$margin, opacity=$alpha)" + } +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/layers/IconLayerManager.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/IconLayerManager.kt new file mode 100644 index 0000000000000..5812d2dcb2250 --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/IconLayerManager.kt @@ -0,0 +1,30 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.layers + +import org.jetbrains.icons.rendering.RenderingContext +import org.jetbrains.icons.impl.rendering.layers.IconLayerRenderer +import org.jetbrains.icons.layers.IconLayer + +interface IconLayerManager { + fun createRenderer(layer: IconLayer, renderingContext: RenderingContext): IconLayerRenderer + + companion object { + @Volatile + private var instance: IconLayerManager? = null + + @JvmStatic + fun getInstance(): IconLayerManager = instance ?: error("IconLayerRendererManager is not initialized") + + fun setInstance(manager: IconLayerManager) { + instance = manager + } + + fun createRenderers( + layers: List, + renderingContext: RenderingContext, + ): List { + val instance = getInstance() + return layers.map { instance.createRenderer(it, renderingContext) } + } + } +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/layers/ImageIconLayer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/ImageIconLayer.kt new file mode 100644 index 0000000000000..79a9cf5ba6d29 --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/ImageIconLayer.kt @@ -0,0 +1,35 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.layers + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.layers.IconLayer +import org.jetbrains.icons.modifiers.IconModifier +import org.jetbrains.icons.rendering.ImageResourceLoader + +@Serializable +class ImageIconLayer( + val loader: ImageResourceLoader, + override val modifier: IconModifier +) : IconLayer { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ImageIconLayer + + if (loader != other.loader) return false + if (modifier != other.modifier) return false + + return true + } + + override fun hashCode(): Int { + var result = loader.hashCode() + result = 31 * result + modifier.hashCode() + return result + } + + override fun toString(): String { + return "ImageIconLayer(loader=$loader, modifier=$modifier)" + } +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/layers/LayoutIconLayer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/LayoutIconLayer.kt new file mode 100644 index 0000000000000..e2be1efa58d7c --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/LayoutIconLayer.kt @@ -0,0 +1,44 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.layers + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.design.IconUnit +import org.jetbrains.icons.layers.IconLayer +import org.jetbrains.icons.modifiers.IconModifier + +@Serializable +class LayoutIconLayer( + val nestedLayers: List, + val direction: LayoutDirection, + val spacing: IconUnit, + override val modifier: IconModifier +) : IconLayer { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as LayoutIconLayer + + if (nestedLayers != other.nestedLayers) return false + if (direction != other.direction) return false + if (modifier != other.modifier) return false + + return true + } + + override fun hashCode(): Int { + var result = nestedLayers.hashCode() + result = 31 * result + direction.hashCode() + result = 31 * result + modifier.hashCode() + return result + } + + override fun toString(): String { + return "ColumnIconLayer(nestedLayers=$nestedLayers, direction=$direction, modifier=$modifier)" + } + + enum class LayoutDirection { + Row, + Column + } +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/layers/ModifierHelpers.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/ModifierHelpers.kt new file mode 100644 index 0000000000000..845607599120c --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/ModifierHelpers.kt @@ -0,0 +1,29 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.layers + +import org.jetbrains.icons.layers.IconLayer +import org.jetbrains.icons.modifiers.CombinedIconModifier +import org.jetbrains.icons.modifiers.IconModifier + +internal inline fun IconLayer.findModifier(): TModifier? { + var output: TModifier? = null + traverseModifiers(modifier) { + if (it is TModifier) { + output = it + return@traverseModifiers false + } + return@traverseModifiers true + } + return output +} + +private fun traverseModifiers(modifier: IconModifier, traverser: (IconModifier) -> Boolean): Boolean { + if (traverser(modifier)) { + if (modifier is CombinedIconModifier) { + if (traverseModifiers(modifier.other, traverser)) { + return traverseModifiers(modifier.root, traverser) + } else return false + } else return true + } + return false +} diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/layers/NaivePolymorphicSerializer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/NaivePolymorphicSerializer.kt new file mode 100644 index 0000000000000..8c79f5f4703eb --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/NaivePolymorphicSerializer.kt @@ -0,0 +1,77 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.layers + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.InternalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.SerialKind +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.descriptors.buildSerialDescriptor +import kotlinx.serialization.encoding.CompositeDecoder +import kotlinx.serialization.encoding.CompositeEncoder +import kotlinx.serialization.encoding.decodeStructure +import kotlinx.serialization.encoding.encodeStructure + +abstract class NaivePolymorphicSerializer(private val name: String): KSerializer { + @OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class) + override val descriptor: SerialDescriptor by lazy(LazyThreadSafetyMode.PUBLICATION) { + buildClassSerialDescriptor(name) { + element("kind", String.serializer().descriptor) + element( + "value", + buildSerialDescriptor(name + "#Value", SerialKind.CONTEXTUAL) + ) + } + } + + @OptIn(ExperimentalSerializationApi::class) + override fun deserialize(decoder: kotlinx.serialization.encoding.Decoder): Type { + return decoder.decodeStructure(descriptor) { + var type: String? = null + var value: Any? = null + if (decodeSequentially()) { + return@decodeStructure decodeSequentially(this) + } + + mainLoop@ while (true) { + when (val index = decodeElementIndex(descriptor)) { + CompositeDecoder.DECODE_DONE -> { + break@mainLoop + } + 0 -> { + type = decodeStringElement(descriptor, index) + } + 1 -> { + type = requireNotNull(type) { "Cannot read polymorphic value before its type token" } + value = decodeValue(descriptor, index, type) + } + else -> throw _root_ide_package_.kotlinx.serialization.SerializationException( + "Invalid index in polymorphic deserialization of " + + (type ?: "unknown class") + + "\n Expected 0, 1 or DECODE_DONE(-1), but found $index" + ) + } + } + @Suppress("UNCHECKED_CAST") + requireNotNull(value) { "Value has not been read for type $type" } as Type + } + } + + private fun decodeSequentially(compositeDecoder: CompositeDecoder): Type { + val type = compositeDecoder.decodeStringElement(descriptor, 0) + return compositeDecoder.decodeValue(descriptor, 1, type) + } + + override fun serialize(encoder: kotlinx.serialization.encoding.Encoder, value: Type) { + encoder.encodeStructure(descriptor) { + encodeStringElement(descriptor, 0, getActualType(value)) + encodeValue(descriptor, 1, value) + } + } + + protected abstract fun getActualType(value: Type): String + protected abstract fun CompositeEncoder.encodeValue(descriptor: SerialDescriptor, index: Int, value: Type) + protected abstract fun CompositeDecoder.decodeValue(descriptor: SerialDescriptor, index: Int, type: String): Type +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/layers/ShapeIconLayer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/ShapeIconLayer.kt new file mode 100644 index 0000000000000..dd13333c49d6d --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/ShapeIconLayer.kt @@ -0,0 +1,40 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.layers + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.design.Shape +import org.jetbrains.icons.design.Color +import org.jetbrains.icons.layers.IconLayer +import org.jetbrains.icons.modifiers.IconModifier + +@Serializable +class ShapeIconLayer( + val color: Color, + val shape: Shape, + override val modifier: IconModifier +): IconLayer { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ShapeIconLayer + + if (color != other.color) return false + if (shape != other.shape) return false + if (modifier != other.modifier) return false + + return true + } + + override fun hashCode(): Int { + var result = color.hashCode() + result = 31 * result + shape.hashCode() + result = 31 * result + modifier.hashCode() + return result + } + + override fun toString(): String { + return "BadgeIconLayer(color=$color, badgeShape=$shape, modifier=$modifier)" + } + +} diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/AwtImageResource.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/AwtImageResource.kt new file mode 100644 index 0000000000000..82fda97aa6671 --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/AwtImageResource.kt @@ -0,0 +1,84 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.rendering + +import org.jetbrains.icons.rendering.BitmapImageResource +import org.jetbrains.icons.rendering.lowlevel.GPUImageResourceHolder +import java.awt.Image +import java.awt.Transparency +import java.awt.color.ColorSpace +import java.awt.image.BufferedImage +import java.awt.image.ComponentColorModel +import java.awt.image.DataBuffer +import java.awt.image.Raster + +class AwtImageResource( + val image: Image +) : CachedGPUImageResourceHolder(), BitmapImageResource { + private val bufferedIamge by lazy { + if (image is BufferedImage) return@lazy image + val bi = BufferedImage( + image.getWidth(null), + image.getHeight(null), + BufferedImage.TYPE_INT_ARGB + ) + val g = bi.createGraphics() + g.drawImage(image, 0, 0, null) + g.dispose() + return@lazy bi + } + + override fun getRGBPixels(): IntArray { + return bufferedIamge.getRGB(0, 0, bufferedIamge.raster.width, bufferedIamge.raster.height, null, 0, bufferedIamge.raster.width) + } + + override fun readPrefetchedPixel(pixels: IntArray, x: Int, y: Int): Int? { + return pixels.getOrNull(y * bufferedIamge.raster.width + x) + } + + override fun getBandOffsetsToSRGB(): IntArray { + return intArrayOf(0, 1, 2, 3) + } + + override val width: Int = image.getWidth(null) + override val height: Int = image.getHeight(null) +} + +fun BitmapImageResource.awtImage(): Image { + if (this is AwtImageResource) return image + val cache = (this as? GPUImageResourceHolder) + return cache?.getOrGenerateBitmap(Image::class) { + awtImageWithoutCaching() + } ?: awtImageWithoutCaching() +} + +private fun BitmapImageResource.awtImageWithoutCaching(): Image { + val pxs = getRGBPixels() + val order = getBandOffsetsToSRGB() + val raster = Raster.createInterleavedRaster( + DirectDataBuffer(pxs), + this.width, + this.height, + this.width * 4, + 4, + order, + null + ) + val colorModel = ComponentColorModel( + ColorSpace.getInstance(ColorSpace.CS_sRGB), + true, + false, + Transparency.TRANSLUCENT, + DataBuffer.TYPE_BYTE + ) + return BufferedImage(colorModel, raster!!, false, null) +} + +private class DirectDataBuffer(val pixels: IntArray) : DataBuffer(TYPE_BYTE, pixels.size) { + override fun getElem(bank: Int, index: Int): Int { + return pixels[index] + } + + override fun setElem(bank: Int, index: Int, value: Int) { + throw UnsupportedOperationException("no write access") + } +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/CachedGPUImageResourceHolder.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/CachedGPUImageResourceHolder.kt new file mode 100644 index 0000000000000..599b4c09c67bd --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/CachedGPUImageResourceHolder.kt @@ -0,0 +1,18 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.rendering + +import org.jetbrains.icons.rendering.lowlevel.GPUImageResourceHolder +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentMap +import kotlin.reflect.KClass + +open class CachedGPUImageResourceHolder: GPUImageResourceHolder { + private val cache: ConcurrentMap, Any> = ConcurrentHashMap() + + override fun getOrGenerateBitmap(bitmapClass: KClass, generator: () -> TBitmap): TBitmap { + val bitmap = cache.computeIfAbsent(bitmapClass) { generator() } + if (bitmap == null || !bitmapClass.isInstance(bitmap)) error("Unexpected type of cached bitmap") + @Suppress("UNCHECKED_CAST") + return bitmap as TBitmap + } +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/CoroutineBasedMutableIconUpdateFlow.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/CoroutineBasedMutableIconUpdateFlow.kt new file mode 100644 index 0000000000000..f0102b588321a --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/CoroutineBasedMutableIconUpdateFlow.kt @@ -0,0 +1,29 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.rendering + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.launch +import org.jetbrains.icons.Icon + +class CoroutineBasedMutableIconUpdateFlow( + private val coroutineScope: CoroutineScope, + updateCallback: (Int) -> Unit +): MutableIconUpdateFlowBase(updateCallback) { + override fun MutableSharedFlow.emitDelayed(delay: Long, value: Int) { + coroutineScope.launch { + delay(delay) + if (handleRateLimiting()) return@launch + emit(value) + updateCallback(value) + } + } + + override fun collectDynamic(flow: Flow, handler: (Icon) -> Unit) { + coroutineScope.launch { + flow.collect { handler(it) } + } + } +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultDynamicIconRenderer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultDynamicIconRenderer.kt new file mode 100644 index 0000000000000..5cd9597efc4d8 --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultDynamicIconRenderer.kt @@ -0,0 +1,47 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.rendering + +import org.jetbrains.icons.DynamicIcon +import org.jetbrains.icons.Icon +import org.jetbrains.icons.InternalIconsApi +import org.jetbrains.icons.rendering.Dimensions +import org.jetbrains.icons.rendering.IconRenderer +import org.jetbrains.icons.rendering.LoadingStrategy +import org.jetbrains.icons.rendering.PaintingApi +import org.jetbrains.icons.rendering.RenderingContext +import org.jetbrains.icons.rendering.ScalingContext +import org.jetbrains.icons.rendering.createRenderer +import java.lang.ref.WeakReference + +internal class DefaultDynamicIconRenderer( + override val icon: DynamicIcon, + val renderingContext: RenderingContext, + loadingStrategy: LoadingStrategy +): IconRenderer { + private var currentIcon = icon.getCurrentIcon() + private var renderer = currentIcon.createRenderer(renderingContext, loadingStrategy) + + init { + val ref = WeakReference(this) + renderingContext.updateFlow.collectDynamic(icon.getFlow()) { + ref.get()?.swapIcons(it) + } + } + + private fun swapIcons(newIcon: Icon) { + if (currentIcon === newIcon) return + currentIcon = newIcon + renderer = newIcon.createRenderer(renderingContext, LoadingStrategy.RenderPlaceholder(renderer)) + renderingContext.updateFlow.triggerUpdate() + } + + @InternalIconsApi + override fun render(api: PaintingApi) { + renderer.render(api) + } + + @InternalIconsApi + override fun calculateExpectedDimensions(scaling: ScalingContext): Dimensions { + return renderer.calculateExpectedDimensions(scaling) + } +} diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultIconRenderer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultIconRenderer.kt new file mode 100644 index 0000000000000..a0aba252585b9 --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultIconRenderer.kt @@ -0,0 +1,51 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.rendering + +import org.jetbrains.icons.Icon +import org.jetbrains.icons.rendering.Dimensions +import org.jetbrains.icons.rendering.IconRenderer +import org.jetbrains.icons.rendering.LoadingStrategy +import org.jetbrains.icons.rendering.PaintingApi +import org.jetbrains.icons.rendering.RenderingContext +import org.jetbrains.icons.rendering.ScalingContext +import org.jetbrains.icons.impl.DefaultLayeredIcon +import org.jetbrains.icons.impl.rendering.layers.IconLayerRenderer +import org.jetbrains.icons.impl.layers.IconLayerManager + +class DefaultIconRenderer( + val iconInstance: DefaultLayeredIcon, + private val context: RenderingContext, + private val loadingStrategy: LoadingStrategy +) : IconRenderer { + override val icon: Icon = iconInstance + private var isLoaded = false + private val layerRenderers = createRenderers() + + private fun createRenderers(): List { + val manager = IconLayerManager.getInstance() + val renderers = iconInstance.layers.map { manager.createRenderer(it, context) } + isLoaded = true + return renderers + } + + override fun render(api: PaintingApi) { + for (layer in layerRenderers) { + layer.render(api) + } + } + + override fun calculateExpectedDimensions(scaling: ScalingContext): Dimensions { + var width = 0 + var height = 0 + for (layer in layerRenderers) { + val dimensions = layer.calculateExpectedDimensions(scaling) + if (dimensions.width > width) { + width = dimensions.width + } + if (dimensions.height > height) { + height = dimensions.height + } + } + return Dimensions(width, height) + } +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultIconRendererManager.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultIconRendererManager.kt new file mode 100644 index 0000000000000..9a2d20f00f2f4 --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultIconRendererManager.kt @@ -0,0 +1,66 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.rendering + +import org.jetbrains.icons.Icon +import org.jetbrains.icons.rendering.IconRenderer +import org.jetbrains.icons.rendering.IconRendererManager +import org.jetbrains.icons.rendering.LoadingStrategy +import org.jetbrains.icons.impl.layers.AnimatedIconLayer +import org.jetbrains.icons.impl.DefaultLayeredIcon +import org.jetbrains.icons.impl.layers.IconIconLayer +import org.jetbrains.icons.impl.layers.ImageIconLayer +import org.jetbrains.icons.impl.layers.LayoutIconLayer +import org.jetbrains.icons.impl.rendering.layers.AnimatedIconLayerRenderer +import org.jetbrains.icons.impl.rendering.layers.IconIconLayerRenderer +import org.jetbrains.icons.layers.IconLayer +import org.jetbrains.icons.impl.rendering.layers.IconLayerRenderer +import org.jetbrains.icons.impl.layers.IconLayerManager +import org.jetbrains.icons.rendering.RenderingContext +import org.jetbrains.icons.impl.DefaultDynamicIcon +import org.jetbrains.icons.impl.layers.ShapeIconLayer +import org.jetbrains.icons.impl.rendering.layers.ShapeIconLayerRenderer +import org.jetbrains.icons.impl.rendering.layers.ImageIconLayerRenderer +import org.jetbrains.icons.impl.rendering.layers.LayoutIconLayerRenderer + +abstract class DefaultIconRendererManager: IconRendererManager, IconLayerManager { + init { + IconLayerManager.setInstance(this) + } + + override fun createRenderer(icon: Icon, context: RenderingContext, loadingStrategy: LoadingStrategy): IconRenderer { + return createRendererOrNull(icon, context, loadingStrategy) ?: error("Unsupported icon type: $icon") + } + + protected fun createRendererOrNull(icon: Icon, context: RenderingContext, loadingStrategy: LoadingStrategy): IconRenderer? { + return when (icon) { + is DefaultLayeredIcon -> DefaultIconRenderer(icon, context, loadingStrategy) + is DefaultDynamicIcon -> DefaultDynamicIconRenderer(icon, context, loadingStrategy) + else -> null + } + } + + override fun createRenderer(layer: IconLayer, renderingContext: RenderingContext): IconLayerRenderer { + return createRendererOrNull(layer, renderingContext) ?: error("Unsupported icon layer type: $layer") + } + + protected fun createRendererOrNull(layer: IconLayer, renderingContext: RenderingContext): IconLayerRenderer? { + return when (layer) { + is ImageIconLayer -> { + ImageIconLayerRenderer(layer, renderingContext) + } + is IconIconLayer -> { + IconIconLayerRenderer(layer, renderingContext) + } + is LayoutIconLayer -> { + LayoutIconLayerRenderer(layer, renderingContext) + } + is AnimatedIconLayer -> { + AnimatedIconLayerRenderer(layer, renderingContext) + } + is ShapeIconLayer -> { + ShapeIconLayerRenderer(layer, renderingContext) + } + else -> null + } + } +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultImageModifiers.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultImageModifiers.kt new file mode 100644 index 0000000000000..7bfa66f9f83bd --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultImageModifiers.kt @@ -0,0 +1,42 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.rendering + +import org.jetbrains.icons.design.Color +import org.jetbrains.icons.filters.ColorFilter +import org.jetbrains.icons.patchers.SvgPatcher +import org.jetbrains.icons.rendering.ImageModifiers + +class DefaultImageModifiers( + override val colorFilter: ColorFilter? = null, + override val svgPatcher: SvgPatcher? = null, + val isDark: Boolean = false, + val stroke: Color? = null +): ImageModifiers { + override fun toString(): String { + return "DefaultImageModifiers(colorFilter=$colorFilter, svgPatcher=$svgPatcher, isDark=$isDark, stroke=$stroke)" + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as DefaultImageModifiers + + if (isDark != other.isDark) return false + if (colorFilter != other.colorFilter) return false + if (svgPatcher != other.svgPatcher) return false + if (stroke != other.stroke) return false + + return true + } + + override fun hashCode(): Int { + var result = isDark.hashCode() + result = 31 * result + (colorFilter?.hashCode() ?: 0) + result = 31 * result + (svgPatcher?.hashCode() ?: 0) + result = 31 * result + (stroke?.hashCode() ?: 0) + return result + } + + +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultImageResourceProvider.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultImageResourceProvider.kt new file mode 100644 index 0000000000000..57b6ed101c521 --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultImageResourceProvider.kt @@ -0,0 +1,10 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.rendering + +import org.jetbrains.icons.rendering.ImageModifiers +import org.jetbrains.icons.rendering.ImageResource +import org.jetbrains.icons.rendering.ImageResourceProvider + +abstract class DefaultImageResourceProvider: ImageResourceProvider { + abstract fun fromSwingIcon(icon: javax.swing.Icon, imageModifiers: ImageModifiers? = null): ImageResource +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/IconAnimationFrame.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/IconAnimationFrame.kt new file mode 100644 index 0000000000000..825afb2c6eca6 --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/IconAnimationFrame.kt @@ -0,0 +1,33 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.rendering + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.layers.IconLayer + +@Serializable +class IconAnimationFrame( + val layers: List, + val duration: Long +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as IconAnimationFrame + + if (duration != other.duration) return false + if (layers != other.layers) return false + + return true + } + + override fun hashCode(): Int { + var result = duration.hashCode() + result = 31 * result + layers.hashCode() + return result + } + + override fun toString(): String { + return "IconAnimationFrame(duration=$duration, layers=$layers)" + } +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/MutableIconUpdateFlowBase.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/MutableIconUpdateFlowBase.kt new file mode 100644 index 0000000000000..f8ac597c2fddd --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/MutableIconUpdateFlowBase.kt @@ -0,0 +1,39 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.rendering + +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.flow.MutableSharedFlow +import org.jetbrains.icons.rendering.MutableIconUpdateFlow +import java.util.concurrent.atomic.AtomicInteger + +abstract class MutableIconUpdateFlowBase( + protected val updateCallback: (Int) -> Unit +): MutableIconUpdateFlow { + private val updateCounter = AtomicInteger() + private val underlayingFlow = MutableSharedFlow() + protected var stopwatch = System.currentTimeMillis() + protected val minimalUpdateMillis = 1000L / 60L // Use actual fps? + + protected fun handleRateLimiting(): Boolean { + if (System.currentTimeMillis() - stopwatch < minimalUpdateMillis) return true + stopwatch = System.currentTimeMillis() + return false + } + + override fun triggerUpdate() { + if (handleRateLimiting()) return + val updateId = updateCounter.incrementAndGet() + underlayingFlow.tryEmit(updateId) + updateCallback(updateId) + } + + override fun triggerDelayedUpdate(delay: Long) { + underlayingFlow.emitDelayed(delay, updateCounter.incrementAndGet()) + } + + protected abstract fun MutableSharedFlow.emitDelayed(delay: Long, value: Int) + + override suspend fun collect(collector: FlowCollector) { + underlayingFlow.collect(collector) + } +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingIcon.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingIcon.kt new file mode 100644 index 0000000000000..75ca6221ee4d4 --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingIcon.kt @@ -0,0 +1,63 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.rendering + +import org.jetbrains.icons.Icon +import org.jetbrains.icons.rendering.IconRendererManager +import org.jetbrains.icons.rendering.RenderingContext +import org.jetbrains.icons.rendering.ScalingContext +import org.jetbrains.icons.impl.rendering.layers.applyTo +import java.awt.Component +import java.awt.Graphics +import java.awt.Graphics2D +import java.awt.image.BufferedImage + +class SwingIcon( + val icon: Icon +): javax.swing.Icon { + private val renderer by lazy { + IconRendererManager.getInstance().createRenderer(icon, RenderingContext.Empty) // TODO listen for updates to redraw Icon + } + private val dimensions by lazy { + renderer.calculateExpectedDimensions(SwingScalingContext(1f)) + } + + override fun paintIcon(c: Component, g: Graphics, x: Int, y: Int) { + val scaling = getScaling(g) + withLayer(scaling, c, g, x, y) { newGraphics -> + val swingApi = SwingPaintingApi(c, newGraphics, 0, 0, scaling = scaling) + renderer.render(swingApi) + } + } + + private fun withLayer(scaling: ScalingContext, c: Component, g: Graphics, x: Int, y: Int, painting: (Graphics2D) -> Unit) { + val w = scaling.applyTo(c.width - x) + val h = scaling.applyTo(c.height - y) + if (w < 0 || h < 0) return + val img = BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB) + val sublayer = img.createGraphics() + try { + painting(sublayer as Graphics2D) + g.drawImage(img, x, y, c.width, c.height, 0, 0, img.width, img.height, null) + } finally { + sublayer.dispose() + } + } + + private fun Graphics2D.clearTransform() { + scale(1 / transform.scaleX, 1 / transform.scaleY) + } + + private fun getScaling(g: Graphics?): SwingScalingContext { + if (g is Graphics2D) { + return SwingScalingContext(g.transform.scaleX.toFloat()) + } else return SwingScalingContext(1f) + } + + override fun getIconWidth(): Int { + return dimensions.width + } + + override fun getIconHeight(): Int { + return dimensions.height + } +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingPaintingApi.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingPaintingApi.kt new file mode 100644 index 0000000000000..eaf58001af6da --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingPaintingApi.kt @@ -0,0 +1,212 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.rendering + +import org.jetbrains.icons.design.Color +import org.jetbrains.icons.filters.ColorFilter +import org.jetbrains.icons.rendering.BitmapImageResource +import org.jetbrains.icons.rendering.Bounds +import org.jetbrains.icons.rendering.DrawMode +import org.jetbrains.icons.rendering.FitAreaScale +import org.jetbrains.icons.rendering.ImageResource +import org.jetbrains.icons.rendering.PaintingApi +import org.jetbrains.icons.rendering.RescalableImageResource +import org.jetbrains.icons.rendering.ScalingContext +import java.awt.AlphaComposite +import java.awt.Component +import java.awt.Graphics +import java.awt.Graphics2D +import java.awt.Image +import java.awt.RenderingHints +import java.awt.geom.Ellipse2D +import java.awt.geom.Rectangle2D + +class SwingPaintingApi( + val c: Component?, + val g: Graphics, + val x: Int, + val y: Int, + private val customWidth: Int? = null, + private val customHeight: Int? = null, + private val overrideColorFilter: ColorFilter? = null, + override val scaling: ScalingContext = SwingScalingContext(1f) +) : PaintingApi { + override val bounds: Bounds get() { + if (c == null) return Bounds(0, 0, customWidth ?: 0, customHeight ?: 0) + return Bounds( + (x * scaling.display).toInt(), + (y * scaling.display).toInt(), + customWidth ?: (c.width * scaling.display).toInt(), + customHeight ?: (c.height * scaling.display).toInt() + ) + } + + override fun getUsedBounds(): Bounds = bounds + + override fun withCustomContext(bounds: Bounds, overrideColorFilter: ColorFilter?): PaintingApi { + return SwingPaintingApi(c, g, bounds.x, bounds.y, bounds.width, bounds.height, overrideColorFilter ?: this.overrideColorFilter) + } + + override fun drawCircle(color: Color, x: Int, y: Int, radius: Float, alpha: Float, mode: DrawMode) { + val r = radius.toDouble() + drawShape(color, Ellipse2D.Double(x - r, y - r, r + r, r + r), alpha, mode) + } + + override fun drawRect(color: Color, x: Int, y: Int, width: Int, height: Int, alpha: Float, mode: DrawMode) { + drawShape(color, Rectangle2D.Double(x.toDouble(), y.toDouble(), width.toDouble(), height.toDouble()), alpha, mode) + } + + private fun drawShape(color: Color, shape: java.awt.Shape, alpha: Float, mode: DrawMode) { + setDrawingDefaults() + if (g is Graphics2D) { + val oldComposite = g.composite + val oldPaint = g.paint + try { + if (mode == DrawMode.Clear) { + g.composite = AlphaComposite.Clear + } + g.color = color.toAwtColor() + if (mode == DrawMode.Stroke) { + g.draw(shape) + } else { + g.fill(shape) + } + } finally { + g.composite = oldComposite + g.paint = oldPaint + } + } + } + + override fun drawImage( + image: ImageResource, + x: Int, + y: Int, + width: Int?, + height: Int?, + srcX: Int, + srcY: Int, + srcWidth: Int?, + srcHeight: Int?, + alpha: Float, + colorFilter: ColorFilter? + ) { + when (image) { + is BitmapImageResource -> { + drawImage(image, x, y, width, height, srcX, srcY, srcWidth, srcHeight, alpha, colorFilter) + } + is RescalableImageResource -> { + drawImage(image, x, y, width, height, srcX, srcY, srcWidth, srcHeight, alpha, colorFilter) + } + } + } + + private fun setDrawingDefaults() { + if (g !is Graphics2D) return + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) + g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE) + } + + private fun drawImage( + image: RescalableImageResource, + x: Int, + y: Int, + width: Int?, + height: Int?, + srcX: Int, + srcY: Int, + srcWidth: Int?, + srcHeight: Int?, + alpha: Float, + colorFilter: ColorFilter? + ) { + val swingImage = image.scale(FitAreaScale(width ?: bounds.width, height ?: bounds.height)) + drawImage(swingImage, x, y, width, height, srcX, srcY, srcWidth, srcHeight, alpha, colorFilter) + } + + private fun drawImage( + image: BitmapImageResource, + x: Int, + y: Int, + width: Int?, + height: Int?, + srcX: Int, + srcY: Int, + srcWidth: Int?, + srcHeight: Int?, + alpha: Float, + colorFilter: ColorFilter? + ) { + val swingImage = image.awtImage() + + drawImage( + swingImage, + x, + y, + width, + height, + srcX, + srcY, + srcWidth, + srcHeight, + alpha, + colorFilter + ) + } + + private fun drawImage( + image: Image, + x: Int, + y: Int, + width: Int?, + height: Int?, + srcX: Int, + srcY: Int, + srcWidth: Int?, + srcHeight: Int?, + alpha: Float, + colorFilter: ColorFilter? + ) { + // TODO apply alpha & color filters + + val imageWidth = image.getWidth(null) + val imageHeight = image.getHeight(null) + + if (imageWidth == 0 || imageHeight == 0) return + setDrawingDefaults() + g.drawImage( + image, + x, + y, + x + (width ?: imageWidth), + y + (height ?: imageHeight), + srcX, + srcY, + srcX + (srcWidth ?: imageWidth), + srcY + (srcHeight ?: imageHeight), + null, + ) + } +} + +fun PaintingApi.swing(): SwingPaintingApi? = this as? SwingPaintingApi + +internal class SwingScalingContext( + override val display: Float +): ScalingContext { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as SwingScalingContext + + return display == other.display + } + + override fun hashCode(): Int { + return display.hashCode() + } + + override fun toString(): String { + return "SwingScalingContext(display=$display)" + } +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/AnimatedIconLayerRenderer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/AnimatedIconLayerRenderer.kt new file mode 100644 index 0000000000000..1c6349594e259 --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/AnimatedIconLayerRenderer.kt @@ -0,0 +1,89 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.rendering.layers + +import org.jetbrains.icons.rendering.Bounds +import org.jetbrains.icons.rendering.Dimensions +import org.jetbrains.icons.rendering.PaintingApi +import org.jetbrains.icons.rendering.RenderingContext +import org.jetbrains.icons.rendering.ScalingContext +import org.jetbrains.icons.impl.layers.AnimatedIconLayer +import org.jetbrains.icons.impl.layers.IconLayerManager +import org.jetbrains.icons.impl.rendering.IconAnimationFrame +import org.jetbrains.icons.impl.rendering.modifiers.applyTo + +class AnimatedIconLayerRenderer( + private val layer: AnimatedIconLayer, + private val renderingContext: RenderingContext +) : IconLayerRenderer { + private val currentContext = renderingContext.adjustTo(layer) + private val frameRenderers = layer.frames.map { AnimatedIconFrameRenderer(it, currentContext) } + private var lastFrame = FrameData(System.currentTimeMillis(), 0, 0) + + override fun render(api: PaintingApi) { + val layout = DefaultLayerLayout( + Bounds( + 0, + 0, + api.bounds.width, + api.bounds.height, + ), + api.bounds + ) + val nestedLayerApi = api.withCustomContext(layer.modifier.applyTo(layout, api.scaling).calculateFinalBounds()) + val currentFrameData = calculateAndSetNewFrameData() + frameRenderers[currentFrameData.frame].render(nestedLayerApi) + if (currentFrameData.remainingDuration > 0L) { + renderingContext.updateFlow.triggerDelayedUpdate(currentFrameData.remainingDuration) + } + } + + override fun calculateExpectedDimensions(scaling: ScalingContext): Dimensions { + return frameRenderers[lastFrame.frame].calculateExpectedDimensions(scaling) + } + + private fun calculateAndSetNewFrameData(): FrameData { + val currentLastFrame = lastFrame + 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 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) + + fun render(api: PaintingApi) { + for (layer in renderers) { + layer.render(api) + } + } + + fun calculateExpectedDimensions(scalingContext: ScalingContext): Dimensions { + var width = 0 + var height = 0 + for (layer in renderers) { + val dimensions = layer.calculateExpectedDimensions(scalingContext) + if (dimensions.width > width) { + width = dimensions.width + } + if (dimensions.height > height) { + height = dimensions.height + } + } + return Dimensions(width, height) + } + } +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/DefaultLayerLayout.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/DefaultLayerLayout.kt new file mode 100644 index 0000000000000..ab19f3e8eaf38 --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/DefaultLayerLayout.kt @@ -0,0 +1,83 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.rendering.layers + +import org.jetbrains.icons.design.Color +import org.jetbrains.icons.filters.ColorFilter +import org.jetbrains.icons.rendering.Bounds + +interface LayerLayout { + val layerBounds: Bounds + val parentBounds: Bounds + val colorFilter: ColorFilter? + val alpha: Float + val cutoutMargin: Float? + val stroke: Color? + + fun copy( + layerBounds: Bounds = this.layerBounds, + parentBounds: Bounds = this.parentBounds, + colorFilter: ColorFilter? = this.colorFilter, + alpha: Float = this.alpha, + cutoutMargin: Float? = this.cutoutMargin, + stroke: Color? = this.stroke + ): LayerLayout + + fun calculateFinalBounds(): Bounds +} + +open class DefaultLayerLayout( + override val layerBounds: Bounds, + override val parentBounds: Bounds, + override val colorFilter: ColorFilter? = null, + override val alpha: Float = 1f, + override val cutoutMargin: Float? = null, + override val stroke: Color? = null +): LayerLayout { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as DefaultLayerLayout + + if (alpha != other.alpha) return false + if (layerBounds != other.layerBounds) return false + if (parentBounds != other.parentBounds) return false + if (colorFilter != other.colorFilter) return false + if (cutoutMargin != other.cutoutMargin) return false + if (stroke != other.stroke) return false + + return true + } + + override fun hashCode(): Int { + var result = alpha.hashCode() + result = 31 * result + layerBounds.hashCode() + result = 31 * result + parentBounds.hashCode() + result = 31 * result + (colorFilter?.hashCode() ?: 0) + return result + } + + override fun toString(): String { + return "LayerLayout(layerBounds=$layerBounds, parentBounds=$parentBounds, colorFilter=$colorFilter, alpha=$alpha, stroke=$stroke)" + } + + override fun calculateFinalBounds(): Bounds { + return Bounds( + layerBounds.x + parentBounds.x, + layerBounds.y + parentBounds.y, + layerBounds.width, + layerBounds.height + ) + } + + override fun copy( + layerBounds: Bounds, + parentBounds: Bounds, + colorFilter: ColorFilter?, + alpha: Float, + cutoutMargin: Float?, + stroke: Color? + ): DefaultLayerLayout { + return DefaultLayerLayout(layerBounds, parentBounds, colorFilter, alpha, cutoutMargin, stroke) + } +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/IconIconLayerRenderer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/IconIconLayerRenderer.kt new file mode 100644 index 0000000000000..c4c734d89c59f --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/IconIconLayerRenderer.kt @@ -0,0 +1,37 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.rendering.layers + +import org.jetbrains.icons.rendering.Bounds +import org.jetbrains.icons.rendering.Dimensions +import org.jetbrains.icons.rendering.PaintingApi +import org.jetbrains.icons.rendering.RenderingContext +import org.jetbrains.icons.rendering.ScalingContext +import org.jetbrains.icons.rendering.createRenderer +import org.jetbrains.icons.impl.layers.IconIconLayer +import org.jetbrains.icons.impl.rendering.modifiers.applyTo + +class IconIconLayerRenderer( + private val layer: IconIconLayer, + private val renderingContext: RenderingContext +) : IconLayerRenderer { + private val renderer = layer.icon.createRenderer(renderingContext.adjustTo(layer)) + + override fun render(api: PaintingApi) { + val layout = DefaultLayerLayout( + Bounds( + 0, + 0, + api.bounds.width, + api.bounds.height, + ), + api.bounds + ) + val appliedLayout = layer.modifier.applyTo(layout, api.scaling) + val boundApi = api.withCustomContext(appliedLayout.calculateFinalBounds()) + renderer.render(boundApi) + } + + override fun calculateExpectedDimensions(scaling: ScalingContext): Dimensions { + return renderer.calculateExpectedDimensions(scaling) + } +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/IconLayerRenderer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/IconLayerRenderer.kt new file mode 100644 index 0000000000000..a5f9ff45ebfc8 --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/IconLayerRenderer.kt @@ -0,0 +1,12 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.rendering.layers + +import org.jetbrains.icons.rendering.Dimensions +import org.jetbrains.icons.rendering.PaintingApi +import org.jetbrains.icons.rendering.ScalingContext + +interface IconLayerRenderer { + fun render(api: PaintingApi) + fun calculateExpectedDimensions(scaling: ScalingContext): Dimensions +} + diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/ImageIconLayerRenderer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/ImageIconLayerRenderer.kt new file mode 100644 index 0000000000000..236b207a6288d --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/ImageIconLayerRenderer.kt @@ -0,0 +1,45 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.rendering.layers + +import org.jetbrains.icons.rendering.Bounds +import org.jetbrains.icons.rendering.Dimensions +import org.jetbrains.icons.rendering.ImageResource +import org.jetbrains.icons.rendering.PaintingApi +import org.jetbrains.icons.rendering.RenderingContext +import org.jetbrains.icons.rendering.ScalingContext +import org.jetbrains.icons.rendering.imageResource +import org.jetbrains.icons.layers.IconLayer +import org.jetbrains.icons.impl.layers.ImageIconLayer +import org.jetbrains.icons.impl.rendering.modifiers.applyTo + +class ImageIconLayerRenderer( + override val layer: ImageIconLayer, + override val renderingContext: RenderingContext +) : BaseImageIconLayerRenderer() { + override var image: ImageResource = imageResource(layer.loader, layer.generateImageModifiers(renderingContext)) + override fun calculateExpectedDimensions(scaling: ScalingContext): Dimensions { + return Dimensions(scaling.applyTo(image.width ?: 16), scaling.applyTo(image.height ?: 16)) + } +} + +abstract class BaseImageIconLayerRenderer: IconLayerRenderer { + abstract var image: ImageResource + abstract val layer: IconLayer + protected abstract val renderingContext: RenderingContext + + override fun render(api: PaintingApi) { + val currentImage = image + val layout = DefaultLayerLayout( + Bounds( + 0, + 0, + api.scaling.applyTo(currentImage.width) ?: api.bounds.width, + api.scaling.applyTo(currentImage.height) ?: api.bounds.width, + ), + api.bounds + ) + val appliedLayout = layer.modifier.applyTo(layout, api.scaling) + val finalBounds = appliedLayout.calculateFinalBounds() + api.drawImage(currentImage, finalBounds.x, finalBounds.y, finalBounds.width, finalBounds.height, alpha = appliedLayout.alpha) + } +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/LayerHelpers.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/LayerHelpers.kt new file mode 100644 index 0000000000000..aa159c4651b0a --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/LayerHelpers.kt @@ -0,0 +1,41 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.rendering.layers + +import org.jetbrains.icons.modifiers.ColorFilterModifier +import org.jetbrains.icons.modifiers.StrokeModifier +import org.jetbrains.icons.modifiers.SvgPatcherModifier +import org.jetbrains.icons.rendering.ImageModifiers +import org.jetbrains.icons.rendering.RenderingContext +import org.jetbrains.icons.rendering.ScalingContext +import org.jetbrains.icons.layers.IconLayer +import org.jetbrains.icons.impl.layers.findModifier +import org.jetbrains.icons.impl.rendering.DefaultImageModifiers +import kotlin.math.ceil + +fun IconLayer.generateImageModifiers(renderingContext: RenderingContext? = null): ImageModifiers { + val defaults = renderingContext?.defaultImageModifiers + val knownDefaults = defaults as? DefaultImageModifiers + return DefaultImageModifiers( + colorFilter = findModifier()?.colorFilter ?: defaults?.colorFilter, + svgPatcher = findModifier()?.svgPatcher ?: defaults?.svgPatcher, + isDark = knownDefaults?.isDark ?: false, + stroke = findModifier()?.color ?: knownDefaults?.stroke + ) +} + +fun RenderingContext.adjustTo(layer: IconLayer): RenderingContext { + return copy(defaultImageModifiers = layer.generateImageModifiers(this)) +} + +fun ScalingContext.applyTo(px: Int): Int { + return applyTo(px.toDouble()) +} + +fun ScalingContext.applyTo(px: Double): Int { + return ceil(px * display).toInt() +} + +fun ScalingContext.applyTo(px: Int?): Int? { + if (px == null) return null + return applyTo(px) +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/LayoutIconLayerRenderer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/LayoutIconLayerRenderer.kt new file mode 100644 index 0000000000000..e90776b3caa42 --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/LayoutIconLayerRenderer.kt @@ -0,0 +1,80 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.rendering.layers + +import org.jetbrains.icons.rendering.Bounds +import org.jetbrains.icons.rendering.Dimensions +import org.jetbrains.icons.rendering.PaintingApi +import org.jetbrains.icons.rendering.RenderingContext +import org.jetbrains.icons.rendering.ScalingContext +import org.jetbrains.icons.impl.layers.IconLayerManager +import org.jetbrains.icons.impl.layers.LayoutIconLayer +import org.jetbrains.icons.impl.rendering.modifiers.applyTo +import org.jetbrains.icons.impl.rendering.modifiers.asPixels + +class LayoutIconLayerRenderer( + private val layoutLayer: LayoutIconLayer, + private val renderingContext: RenderingContext, +) : IconLayerRenderer { + private val currentContext = renderingContext.adjustTo(layoutLayer) + private val nestedRenderers = createNestedRenderers(layoutLayer) + + private fun createNestedRenderers(layer: LayoutIconLayer): List { + return IconLayerManager.createRenderers(layer.nestedLayers, currentContext) + } + + override fun render(api: PaintingApi) { + val layout = DefaultLayerLayout( + Bounds( + 0, + 0, + api.bounds.width, + api.bounds.height, + ), + api.bounds, + ) + val appliedLayout = layoutLayer.modifier.applyTo(layout, api.scaling) + val finalBounds = appliedLayout.calculateFinalBounds() + + if (layoutLayer.direction == LayoutIconLayer.LayoutDirection.Row) { + val spacingPx = layoutLayer.spacing.asPixels(api.scaling, api.bounds, true) + val remainingSize = finalBounds.width - spacingPx * (nestedRenderers.count() - 1) + val size = remainingSize / nestedRenderers.count() + var offset = 0 + for (nestedRenderer in nestedRenderers) { + val nestedApi = api.withCustomContext(Bounds(finalBounds.x + offset, finalBounds.y, size, finalBounds.height)) + nestedRenderer.render(nestedApi) + offset += size + spacingPx + } + } else { + val spacingPx = layoutLayer.spacing.asPixels(api.scaling, api.bounds, false) + val remainingSize = finalBounds.height - spacingPx * (nestedRenderers.count() - 1) + val size = remainingSize / nestedRenderers.count() + var offset = 0 + for (nestedRenderer in nestedRenderers) { + val nestedApi = api.withCustomContext(Bounds(finalBounds.x, finalBounds.y + offset, finalBounds.width, size)) + nestedRenderer.render(nestedApi) + offset += size + spacingPx + } + } + } + + override fun calculateExpectedDimensions(scaling: ScalingContext): Dimensions { + var width = 0 + var height = 0 + for (layer in nestedRenderers) { + val dimensions = layer.calculateExpectedDimensions(scaling) + if (layoutLayer.direction == LayoutIconLayer.LayoutDirection.Row) { + width += dimensions.width + if (dimensions.height > height) { + height = dimensions.height + } + } else { + height += dimensions.height + if (dimensions.width > width) { + width = dimensions.width + } + } + } + return Dimensions(width, height) + } +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/ShapeIconLayerRenderer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/ShapeIconLayerRenderer.kt new file mode 100644 index 0000000000000..b7a8f895cdb10 --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/ShapeIconLayerRenderer.kt @@ -0,0 +1,70 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.rendering.layers + +import org.jetbrains.icons.design.Circle +import org.jetbrains.icons.design.Rectangle +import org.jetbrains.icons.rendering.Bounds +import org.jetbrains.icons.rendering.Dimensions +import org.jetbrains.icons.rendering.DrawMode +import org.jetbrains.icons.rendering.PaintingApi +import org.jetbrains.icons.rendering.RenderingContext +import org.jetbrains.icons.rendering.ScalingContext +import org.jetbrains.icons.impl.layers.ShapeIconLayer +import org.jetbrains.icons.impl.rendering.modifiers.applyTo +import kotlin.math.roundToInt + +class ShapeIconLayerRenderer( + val layer: ShapeIconLayer, + val renderingContext: RenderingContext +) : IconLayerRenderer { + override fun render(api: PaintingApi) { + val layout = DefaultLayerLayout( + Bounds( + 0, + 0, + api.bounds.width, + api.bounds.height, + ), + api.bounds + ) + val appliedLayout = layer.modifier.applyTo(layout, api.scaling) + val finalBounds = appliedLayout.calculateFinalBounds() + val cutoutMargin = appliedLayout.cutoutMargin ?: 0f + when (layer.shape) { + Circle -> { + val radius = finalBounds.width.coerceAtMost(finalBounds.height) / 2.0f + val roundRadius = radius.roundToInt() + if (cutoutMargin > 0f) { + api.drawCircle( + layer.color, + finalBounds.x + roundRadius, + finalBounds.y + roundRadius, + radius + cutoutMargin + 1, + 1f, + DrawMode.Clear + ) + } + api.drawCircle(layer.color, finalBounds.x + roundRadius, finalBounds.y + roundRadius, radius, 1f) + } + Rectangle -> { + if (cutoutMargin > 0f) { + val cutoutMargin2 = cutoutMargin + cutoutMargin + api.drawRect( + layer.color, + finalBounds.x - cutoutMargin.roundToInt(), + finalBounds.y - cutoutMargin.roundToInt(), + cutoutMargin2.roundToInt(), + cutoutMargin2.roundToInt(), + 1f, + DrawMode.Clear + ) + } + api.drawRect(layer.color, finalBounds.x, finalBounds.y, finalBounds.width, finalBounds.height, 1f) + } + } + } + + override fun calculateExpectedDimensions(scaling: ScalingContext): Dimensions { + return Dimensions(0, 0) + } +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/modifiers/ColorFilterModifier.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/modifiers/ColorFilterModifier.kt new file mode 100644 index 0000000000000..362d7268986b3 --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/modifiers/ColorFilterModifier.kt @@ -0,0 +1,9 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.rendering.modifiers + +import org.jetbrains.icons.modifiers.ColorFilterModifier +import org.jetbrains.icons.impl.rendering.layers.DefaultLayerLayout + +internal fun applyColorFilterModifier(modifier: ColorFilterModifier, layout: DefaultLayerLayout, displayScale: Float): DefaultLayerLayout { + return layout.copy(colorFilter = modifier.colorFilter) +} diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/modifiers/IconModifier.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/modifiers/IconModifier.kt new file mode 100644 index 0000000000000..96a33a9bb27cf --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/modifiers/IconModifier.kt @@ -0,0 +1,121 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.rendering.modifiers + +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.InternalIconsApi +import org.jetbrains.icons.design.MaxIconUnit +import org.jetbrains.icons.design.DisplayPointIconUnit +import org.jetbrains.icons.design.IconUnit +import org.jetbrains.icons.design.IconVerticalAlign +import org.jetbrains.icons.design.IconHorizontalAlign +import org.jetbrains.icons.design.PercentIconUnit +import org.jetbrains.icons.design.PixelIconUnit +import org.jetbrains.icons.modifiers.AlignIconModifier +import org.jetbrains.icons.modifiers.AlphaIconModifier +import org.jetbrains.icons.modifiers.ColorFilterModifier +import org.jetbrains.icons.modifiers.CombinedIconModifier +import org.jetbrains.icons.modifiers.CutoutMarginModifier +import org.jetbrains.icons.modifiers.HeightIconModifier +import org.jetbrains.icons.modifiers.IconModifier +import org.jetbrains.icons.modifiers.MarginIconModifier +import org.jetbrains.icons.modifiers.StrokeModifier +import org.jetbrains.icons.modifiers.SvgPatcherModifier +import org.jetbrains.icons.modifiers.WidthIconModifier +import org.jetbrains.icons.rendering.Bounds +import org.jetbrains.icons.rendering.ScalingContext +import org.jetbrains.icons.impl.rendering.layers.LayerLayout +import org.jetbrains.icons.impl.rendering.layers.applyTo +import kotlin.math.roundToInt + +@OptIn(ExperimentalIconsApi::class, InternalIconsApi::class) +fun IconModifier.applyTo(layout: LayerLayout, scaling: ScalingContext): LayerLayout { + return when (this) { + is CombinedIconModifier -> { + other.applyTo(root.applyTo(layout, scaling), scaling) + } + is WidthIconModifier -> { + layout.copy( + layerBounds = layout.layerBounds.copy( + width = width.asPixels(scaling, layout.parentBounds, true) + ) + ) + } + is HeightIconModifier -> { + layout.copy( + layerBounds = layout.layerBounds.copy( + height = height.asPixels(scaling, layout.parentBounds, false) + ) + ) + } + is AlignIconModifier -> { + val x = when (align.horizontalAlign) { + IconHorizontalAlign.Left -> { + layout.layerBounds.x + } + IconHorizontalAlign.Right -> { + layout.parentBounds.width - layout.layerBounds.width + } + IconHorizontalAlign.Center -> { + (layout.parentBounds.width / 2) - (layout.layerBounds.width / 2) + } + } + val y = when (align.verticalAlign) { + IconVerticalAlign.Top -> { + layout.layerBounds.y + } + IconVerticalAlign.Bottom -> { + layout.parentBounds.height - layout.layerBounds.height + } + IconVerticalAlign.Center -> { + (layout.parentBounds.height / 2) - (layout.layerBounds.height / 2) + } + } + layout.copy( + layerBounds = layout.layerBounds.copy( + x = x, + y = y + ) + ) + } + is MarginIconModifier -> applyMarginIconModifier(this, layout, scaling) + is AlphaIconModifier -> layout.copy(alpha = alpha) + is ColorFilterModifier -> layout // applyColorFilterModifier(this, layout, displayScale) // ImageModifier + is SvgPatcherModifier -> layout // ImageModifier + is CutoutMarginModifier -> applyCutoutMarginModifier(this, layout, scaling) + is StrokeModifier -> layout + IconModifier.Companion -> layout + } +} + +fun applyStrokeModifier(modifier: StrokeModifier, layout: LayerLayout): LayerLayout { + return layout.copy(stroke = modifier.color) +} + +fun applyCutoutMarginModifier(modifier: CutoutMarginModifier, layout: LayerLayout, scaling: ScalingContext): LayerLayout { + val final = layout.calculateFinalBounds() + val size = modifier.size.asFractionalPixels(scaling, final, true) + return layout.copy(cutoutMargin = size) +} + +fun IconUnit.asFractionalPixels(scaling: ScalingContext, bounds: Bounds, isWidth: Boolean = false): Float { + return when (this) { + is PixelIconUnit -> value + is PercentIconUnit -> if (isWidth) { + bounds.width * value + } else { + bounds.height * value + } + is DisplayPointIconUnit -> scaling.applyTo(value) + is MaxIconUnit -> { + if (isWidth) { + bounds.width + } else { + bounds.height + } + } + }.toFloat() +} + +fun IconUnit.asPixels(scaling: ScalingContext, bounds: Bounds, isWidth: Boolean = false): Int { + return asFractionalPixels(scaling, bounds, isWidth).roundToInt() +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/modifiers/MarginIconModifier.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/modifiers/MarginIconModifier.kt new file mode 100644 index 0000000000000..7427d1fb03c60 --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/modifiers/MarginIconModifier.kt @@ -0,0 +1,25 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.rendering.modifiers + +import org.jetbrains.icons.modifiers.MarginIconModifier +import org.jetbrains.icons.rendering.ScalingContext +import org.jetbrains.icons.impl.rendering.layers.LayerLayout + +internal fun applyMarginIconModifier(modifier: MarginIconModifier, layout: LayerLayout, scaling: ScalingContext): LayerLayout { + val leftPx = modifier.left.asPixels(scaling, layout.parentBounds, true) + val topPx = modifier.top.asPixels(scaling, layout.parentBounds, false) + val rightPx = modifier.right.asPixels(scaling, layout.parentBounds, true) + val bottomPx = modifier.bottom.asPixels(scaling, layout.parentBounds, false) + + val finalX = maxOf(layout.layerBounds.x, leftPx) + val finalY = maxOf(layout.layerBounds.y, topPx) + + return layout.copy( + layerBounds = layout.layerBounds.copy( + x = finalX, + y = finalY, + width = minOf(layout.parentBounds.width - finalX - rightPx, layout.layerBounds.width), + height = minOf(layout.parentBounds.height - finalY - bottomPx, layout.layerBounds.height) + ) + ) +} diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/toAwtColor.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/toAwtColor.kt new file mode 100644 index 0000000000000..267f5f389e615 --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/toAwtColor.kt @@ -0,0 +1,12 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.rendering + +import org.jetbrains.icons.design.Color +import org.jetbrains.icons.design.RGBA + +fun Color.toAwtColor(): java.awt.Color { + @Suppress("UseJBColor") + when (this) { + is RGBA -> return java.awt.Color(red, green, blue, alpha) + } +} \ No newline at end of file diff --git a/platform/ide-core/resources/META-INF/intellij.moduleSets.core.platform.xml b/platform/ide-core/resources/META-INF/intellij.moduleSets.core.platform.xml index 7e4afe5b8938e..d99c66bcd3a84 100644 --- a/platform/ide-core/resources/META-INF/intellij.moduleSets.core.platform.xml +++ b/platform/ide-core/resources/META-INF/intellij.moduleSets.core.platform.xml @@ -130,6 +130,10 @@ + + + + diff --git a/platform/jewel/build.gradle.kts b/platform/jewel/build.gradle.kts index 07dc37ee3c1eb..c52e74aa98200 100644 --- a/platform/jewel/build.gradle.kts +++ b/platform/jewel/build.gradle.kts @@ -6,6 +6,8 @@ plugins { `jewel-linting` } + + tasks { register("clean") { delete(rootProject.layout.buildDirectory) } diff --git a/platform/jewel/gradle/libs.versions.toml b/platform/jewel/gradle/libs.versions.toml index abb7e33534606..927cdea6bc99e 100644 --- a/platform/jewel/gradle/libs.versions.toml +++ b/platform/jewel/gradle/libs.versions.toml @@ -17,6 +17,7 @@ kotlin = "2.2.20" kotlinpoet = "2.1.0" kotlinterGradlePlugin = "5.2.0" kotlinxSerialization = "1.9.0" +kotlinxCoroutines = "1.10.2" ktfmtGradlePlugin = "0.23.0" metalava = "1.0.0-alpha13" jsoup = "1.21.2" @@ -43,8 +44,9 @@ junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "jun junit-platform-engine = { module = "org.junit.platform:junit-platform-engine", version.ref = "junitPlatform" } junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher", version.ref = "junitPlatform" } kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } +kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinxSerialization" } +kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" } -kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core-jvm", version.ref = "kotlinxSerialization" } ktor-client-java = { module = "io.ktor:ktor-client-java", version = "3.0.3" } metalava = { module = "com.android.tools.metalava:metalava", version.ref = "metalava" } jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } diff --git a/platform/jewel/ide-laf-bridge/BUILD.bazel b/platform/jewel/ide-laf-bridge/BUILD.bazel index e6b5b0ba2e838..9e0eb1d239ef9 100644 --- a/platform/jewel/ide-laf-bridge/BUILD.bazel +++ b/platform/jewel/ide-laf-bridge/BUILD.bazel @@ -45,6 +45,7 @@ jvm_library( "//platform/core-ui", "//libraries/compose-foundation-desktop", "//platform/editor-ui-ex:editor-ex", + "//python:python-community-impl", ], exports = [ "//platform/jewel/foundation", @@ -102,6 +103,7 @@ jvm_library( "//plugins/textmate/plugin", "//plugins/textmate/plugin:plugin_test_lib", "@lib//:io-mockk-jvm", + "//python:python-community-impl", ], exports = [ "//platform/jewel/foundation", @@ -123,4 +125,4 @@ jps_test( name = "ide-laf-bridge_test", runtime_deps = [":ide-laf-bridge_test_lib"] ) -### auto-generated section `test intellij.platform.jewel.ideLafBridge` end \ No newline at end of file +### auto-generated section `test intellij.platform.jewel.ideLafBridge` end diff --git a/platform/jewel/ide-laf-bridge/intellij.platform.jewel.ideLafBridge.iml b/platform/jewel/ide-laf-bridge/intellij.platform.jewel.ideLafBridge.iml index 2fd98aefa08fa..9e6e0162d2acd 100644 --- a/platform/jewel/ide-laf-bridge/intellij.platform.jewel.ideLafBridge.iml +++ b/platform/jewel/ide-laf-bridge/intellij.platform.jewel.ideLafBridge.iml @@ -51,10 +51,9 @@ + - - \ No newline at end of file diff --git a/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/JewelComposePanelWrapper.kt b/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/JewelComposePanelWrapper.kt index 0d5e8eefba380..3406d6c88ef5b 100644 --- a/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/JewelComposePanelWrapper.kt +++ b/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/JewelComposePanelWrapper.kt @@ -75,7 +75,7 @@ public fun JewelComposePanel( SwingBridgeTheme { CompositionLocalProvider( LocalComponentFoundation provides this@createJewelComposePanel, - LocalPopupRenderer provides JBPopupRenderer, + LocalPopupRenderer provides JBPopupRenderer ) { ComponentDataProviderBridge(jewelPanel, content = content) } diff --git a/platform/jewel/int-ui/int-ui-standalone/BUILD.bazel b/platform/jewel/int-ui/int-ui-standalone/BUILD.bazel index 4f6699ecedb36..cb3775032f450 100644 --- a/platform/jewel/int-ui/int-ui-standalone/BUILD.bazel +++ b/platform/jewel/int-ui/int-ui-standalone/BUILD.bazel @@ -9,6 +9,7 @@ create_kotlinc_options( "androidx.compose.foundation.ExperimentalFoundationApi", "org.jetbrains.jewel.foundation.ExperimentalJewelApi", "org.jetbrains.jewel.foundation.InternalJewelApi", + "org.jetbrains.icons.api.InternalIconsApi", ], x_context_parameters = True, x_explicit_api_mode = "strict" @@ -37,6 +38,9 @@ jvm_library( "//libraries/compose-runtime-desktop", "//platform/jewel/foundation", "//libraries/jbr", + "//platform/icons-api/rendering", + "//platform/icons-impl", + "@lib//:platform-jewel-int_ui-standalone-jetbrains-kotlinx-serialization-core-jvm", "@lib//:jna", ], plugins = ["@lib//:compose-plugin"] @@ -60,4 +64,4 @@ jvm_library( ], plugins = ["@lib//:compose-plugin"] ) -### auto-generated section `build intellij.platform.jewel.intUi.standalone` end \ No newline at end of file +### auto-generated section `build intellij.platform.jewel.intUi.standalone` end diff --git a/platform/jewel/int-ui/int-ui-standalone/build.gradle.kts b/platform/jewel/int-ui/int-ui-standalone/build.gradle.kts index 7defc34d3b345..226c8cb01a834 100644 --- a/platform/jewel/int-ui/int-ui-standalone/build.gradle.kts +++ b/platform/jewel/int-ui/int-ui-standalone/build.gradle.kts @@ -13,6 +13,9 @@ plugins { dependencies { api(projects.ui) + api(project(":jb-icons-api")) + api(project(":jb-icons-api-rendering")) + api(project(":jb-icons-impl")) implementation(libs.jbr.api) implementation(libs.jna.core) } diff --git a/platform/jewel/int-ui/int-ui-standalone/intellij.platform.jewel.intUi.standalone.iml b/platform/jewel/int-ui/int-ui-standalone/intellij.platform.jewel.intUi.standalone.iml index 37d796bbcb81e..bd01e74366eac 100644 --- a/platform/jewel/int-ui/int-ui-standalone/intellij.platform.jewel.intUi.standalone.iml +++ b/platform/jewel/int-ui/int-ui-standalone/intellij.platform.jewel.intUi.standalone.iml @@ -4,7 +4,7 @@ - @@ -41,6 +41,5 @@ - \ No newline at end of file diff --git a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconDesigner.kt b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconDesigner.kt new file mode 100644 index 0000000000000..b01b0175f4082 --- /dev/null +++ b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconDesigner.kt @@ -0,0 +1,14 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.jewel.intui.standalone.icon + +import org.jetbrains.icons.modifiers.IconModifier +import org.jetbrains.icons.impl.design.DefaultIconDesigner +import org.jetbrains.jewel.ui.icon.PathImageResourceLoader + +internal class StandaloneIconDesigner : DefaultIconDesigner() { + override fun image(path: String, classLoader: ClassLoader?, modifier: IconModifier) { + image(PathImageResourceLoader(path, classLoader), modifier) + } + + override fun createNestedDesigner(): StandaloneIconDesigner = StandaloneIconDesigner() +} diff --git a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconManager.kt b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconManager.kt new file mode 100644 index 0000000000000..3b2054a08459e --- /dev/null +++ b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconManager.kt @@ -0,0 +1,14 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.jewel.intui.standalone.icon + +import org.jetbrains.icons.Icon +import org.jetbrains.icons.design.IconDesigner +import org.jetbrains.icons.impl.DefaultIconManager + +internal class StandaloneIconManager : DefaultIconManager() { + override fun icon(designer: IconDesigner.() -> Unit): Icon { + val iconDesigner = StandaloneIconDesigner() + iconDesigner.designer() + return iconDesigner.build() + } +} diff --git a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconRendererManager.kt b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconRendererManager.kt new file mode 100644 index 0000000000000..4a801f732019f --- /dev/null +++ b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconRendererManager.kt @@ -0,0 +1,50 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +@file:OptIn(ExperimentalIconsApi::class) + +package org.jetbrains.jewel.intui.standalone.icon + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.serialization.KSerializer +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.Icon +import org.jetbrains.icons.rendering.ImageModifiers +import org.jetbrains.icons.rendering.MutableIconUpdateFlow +import org.jetbrains.icons.rendering.RenderingContext +import org.jetbrains.icons.layers.IconLayer +import org.jetbrains.icons.impl.rendering.CoroutineBasedMutableIconUpdateFlow +import org.jetbrains.icons.impl.rendering.DefaultIconRendererManager + +@OptIn(ExperimentalIconsApi::class) +internal class StandaloneIconRendererManager : DefaultIconRendererManager() { + override fun createUpdateFlow(scope: CoroutineScope?, updateCallback: (Int) -> Unit): MutableIconUpdateFlow { + if (scope == null) return EmptyMutableIconUpdateFlow() + return CoroutineBasedMutableIconUpdateFlow(scope, updateCallback) + } + + override fun createRenderingContext( + updateFlow: MutableIconUpdateFlow, + defaultImageModifiers: ImageModifiers?, + ): RenderingContext { + return RenderingContext(updateFlow, defaultImageModifiers) + } +} + +private class EmptyMutableIconUpdateFlow : MutableIconUpdateFlow { + override fun triggerUpdate() { + // Do nothing + } + + override fun triggerDelayedUpdate(delay: Long) { + // Do nothing + } + + override suspend fun collect(collector: FlowCollector) { + // Do nothing + } + + override fun collectDynamic(flow: Flow, handler: (Icon) -> Unit) { + // Do nothing + } +} diff --git a/platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.api.IconManager b/platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.api.IconManager new file mode 100644 index 0000000000000..505761a636497 --- /dev/null +++ b/platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.api.IconManager @@ -0,0 +1 @@ +org.jetbrains.jewel.intui.standalone.icon.StandaloneIconManager diff --git a/platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.api.rendering.IconRendererManager b/platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.api.rendering.IconRendererManager new file mode 100644 index 0000000000000..8ea2a0a84dfc4 --- /dev/null +++ b/platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.api.rendering.IconRendererManager @@ -0,0 +1 @@ +org.jetbrains.jewel.intui.standalone.icon.StandaloneIconRendererManager diff --git a/platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.api.rendering.ImageResourceProvider b/platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.api.rendering.ImageResourceProvider new file mode 100644 index 0000000000000..e94e176216edf --- /dev/null +++ b/platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.api.rendering.ImageResourceProvider @@ -0,0 +1 @@ +org.jetbrains.jewel.ui.icon.ComposeImageResourceProvider diff --git a/platform/jewel/samples/showcase/BUILD.bazel b/platform/jewel/samples/showcase/BUILD.bazel index b839524d27971..952dd32e26a85 100644 --- a/platform/jewel/samples/showcase/BUILD.bazel +++ b/platform/jewel/samples/showcase/BUILD.bazel @@ -9,6 +9,7 @@ create_kotlinc_options( "androidx.compose.foundation.ExperimentalFoundationApi", "org.jetbrains.jewel.foundation.ExperimentalJewelApi", "org.jetbrains.jewel.foundation.InternalJewelApi", + "org.jetbrains.icons.api.ExperimentalIconsApi", ], plugin_options = ["plugin:androidx.compose.compiler.plugins.kotlin:generateFunctionKeyMetaAnnotations=true"], x_context_parameters = True, diff --git a/platform/jewel/samples/showcase/api-dump.txt b/platform/jewel/samples/showcase/api-dump.txt index 907bffeb202f9..66c6bda6f2d84 100644 --- a/platform/jewel/samples/showcase/api-dump.txt +++ b/platform/jewel/samples/showcase/api-dump.txt @@ -2,11 +2,15 @@ f:org.jetbrains.jewel.samples.showcase.ShowcaseIcons - sf:$stable:I - sf:INSTANCE:org.jetbrains.jewel.samples.showcase.ShowcaseIcons - f:getComponentsMenu():org.jetbrains.jewel.ui.icon.PathIconKey -- f:getGitHub():org.jetbrains.jewel.ui.icon.PathIconKey -- f:getJewelLogo():org.jetbrains.jewel.ui.icon.PathIconKey +- f:getGitHub():org.jetbrains.icons.Icon +- f:getGitHubKey():org.jetbrains.jewel.ui.icon.PathIconKey +- f:getJewelLogo():org.jetbrains.icons.Icon +- f:getJewelLogoKey():org.jetbrains.jewel.ui.icon.PathIconKey +- f:getLayeredIcon():org.jetbrains.icons.Icon - f:getMarkdown():org.jetbrains.jewel.ui.icon.PathIconKey - f:getSunny():org.jetbrains.jewel.ui.icon.PathIconKey -- f:getThemeDark():org.jetbrains.jewel.ui.icon.PathIconKey +- f:getThemeDark():org.jetbrains.icons.Icon +- f:getThemeDarkKey():org.jetbrains.jewel.ui.icon.PathIconKey - f:getThemeLight():org.jetbrains.jewel.ui.icon.PathIconKey - f:getThemeLightWithLightHeader():org.jetbrains.jewel.ui.icon.PathIconKey - f:getThemeSystem():org.jetbrains.jewel.ui.icon.PathIconKey diff --git a/platform/jewel/samples/showcase/exposed-third-party-api.txt b/platform/jewel/samples/showcase/exposed-third-party-api.txt index 168de8a3b0bb9..9f3f1e91d6e47 100644 --- a/platform/jewel/samples/showcase/exposed-third-party-api.txt +++ b/platform/jewel/samples/showcase/exposed-third-party-api.txt @@ -1,2 +1,3 @@ androidx/compose/** kotlin/jvm/internal/DefaultConstructorMarker +org/jetbrains/icons/api/Icon diff --git a/platform/jewel/samples/showcase/intellij.platform.jewel.samples.showcase.iml b/platform/jewel/samples/showcase/intellij.platform.jewel.samples.showcase.iml index f95ea989b5614..e747da0e3eb9c 100644 --- a/platform/jewel/samples/showcase/intellij.platform.jewel.samples.showcase.iml +++ b/platform/jewel/samples/showcase/intellij.platform.jewel.samples.showcase.iml @@ -4,7 +4,7 @@ - diff --git a/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/ShowcaseIcons.kt b/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/ShowcaseIcons.kt index 02bd15b92f536..14fd8afc2ccdd 100644 --- a/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/ShowcaseIcons.kt +++ b/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/ShowcaseIcons.kt @@ -1,14 +1,36 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.jewel.samples.showcase +import org.jetbrains.icons.Icon +import org.jetbrains.icons.design.IconAlign +import org.jetbrains.icons.design.percent +import org.jetbrains.icons.icon +import org.jetbrains.icons.imageIcon +import org.jetbrains.icons.modifiers.IconModifier +import org.jetbrains.icons.modifiers.align +import org.jetbrains.icons.modifiers.fillMaxSize +import org.jetbrains.icons.modifiers.margin +import org.jetbrains.icons.modifiers.size import org.jetbrains.jewel.ui.icon.PathIconKey public object ShowcaseIcons { public val componentsMenu: PathIconKey = PathIconKey("icons/structure.svg", ShowcaseIcons::class.java) - public val gitHub: PathIconKey = PathIconKey("icons/github.svg", ShowcaseIcons::class.java) - public val jewelLogo: PathIconKey = PathIconKey("icons/jewel-logo.svg", ShowcaseIcons::class.java) + public val gitHub: Icon = imageIcon("icons/github.svg", ShowcaseIcons::class.java.classLoader, modifier = IconModifier.fillMaxSize()) + public val themeDark: Icon = imageIcon("icons/darkTheme.svg", ShowcaseIcons::class.java.classLoader) + public val jewelLogo: Icon = imageIcon("icons/systemTheme.svg", ShowcaseIcons::class.java.classLoader, modifier = IconModifier.fillMaxSize()) + public val layeredIcon: Icon = icon { + icon(gitHub, IconModifier.fillMaxSize()) + icon( + jewelLogo, + modifier = IconModifier.align(IconAlign.BottomRight) + .size(40.percent) + .margin(25.percent, 0.percent, 0.percent, 0.percent) + ) + } + public val gitHubKey: PathIconKey = PathIconKey("icons/github.svg", ShowcaseIcons::class.java) + public val jewelLogoKey: PathIconKey = PathIconKey("icons/jewel-logo.svg", ShowcaseIcons::class.java) public val markdown: PathIconKey = PathIconKey("icons/markdown.svg", ShowcaseIcons::class.java) - public val themeDark: PathIconKey = PathIconKey("icons/darkTheme.svg", ShowcaseIcons::class.java) + public val themeDarkKey: PathIconKey = PathIconKey("icons/darkTheme.svg", ShowcaseIcons::class.java) public val themeLight: PathIconKey = PathIconKey("icons/lightTheme.svg", ShowcaseIcons::class.java) public val themeLightWithLightHeader: PathIconKey = PathIconKey("icons/lightWithLightHeaderTheme.svg", ShowcaseIcons::class.java) diff --git a/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/components/Icons.kt b/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/components/Icons.kt index 3e1e0769339b9..1010af604ada7 100644 --- a/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/components/Icons.kt +++ b/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/components/Icons.kt @@ -24,12 +24,18 @@ import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.unit.dp +import org.jetbrains.icons.design.Circle +import org.jetbrains.icons.modifiers.IconModifier import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.samples.showcase.ShowcaseIcons import org.jetbrains.jewel.ui.component.CheckboxRow import org.jetbrains.jewel.ui.component.Icon import org.jetbrains.jewel.ui.component.Image import org.jetbrains.jewel.ui.component.Text +import org.jetbrains.jewel.ui.icon.badge +import org.jetbrains.jewel.ui.icon.iconKey +import org.jetbrains.jewel.ui.icon.size +import org.jetbrains.jewel.ui.icon.stroke import org.jetbrains.jewel.ui.icons.AllIconsKeys import org.jetbrains.jewel.ui.painter.badge.DotBadgeShape import org.jetbrains.jewel.ui.painter.hints.Badge @@ -45,12 +51,12 @@ public fun Icons(modifier: Modifier = Modifier) { modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp), ) { - Icon(ShowcaseIcons.jewelLogo, null, Modifier.size(16.dp)) - Icon(ShowcaseIcons.jewelLogo, "Jewel Logo", Modifier.size(32.dp)) - Icon(ShowcaseIcons.jewelLogo, "Jewel Logo", Modifier.size(64.dp)) - Icon(ShowcaseIcons.jewelLogo, "Jewel Logo", Modifier.size(128.dp)) + Icon(ShowcaseIcons.jewelLogoKey, null, Modifier.size(16.dp)) + Icon(ShowcaseIcons.jewelLogoKey, "Jewel Logo", Modifier.size(32.dp)) + Icon(ShowcaseIcons.jewelLogoKey, "Jewel Logo", Modifier.size(64.dp)) + Icon(ShowcaseIcons.jewelLogoKey, "Jewel Logo", Modifier.size(128.dp)) Icon( - key = ShowcaseIcons.jewelLogo, + key = ShowcaseIcons.jewelLogoKey, contentDescription = "Jewel Logo", modifier = Modifier.size(128.dp), colorFilter = ColorFilter.tint(Color.Magenta, BlendMode.Multiply), @@ -106,6 +112,55 @@ public fun Icons(modifier: Modifier = Modifier) { } } + Column { + Text("Icon Modifiers & Layers: (new api)") + + Row( + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + ) { + Box(Modifier.size(24.dp), contentAlignment = Alignment.Center) { + Icon(contentDescription = "taskGroup") { + iconKey(AllIconsKeys.Nodes.ConfigFolder) + } + } + Box(Modifier.size(24.dp), contentAlignment = Alignment.Center) { + Icon(contentDescription = "taskGroup") { + iconKey(AllIconsKeys.Nodes.ConfigFolder) + badge(Color.Red, Circle) + } + } + val backgroundColor = + if (JewelTheme.isDark) { + JewelTheme.colorPalette.blueOrNull(4) ?: Color(0xFF375FAD) + } else { + JewelTheme.colorPalette.blueOrNull(4) ?: Color(0xFF3574F0) + } + Box( + Modifier.size(24.dp).background(backgroundColor, shape = RoundedCornerShape(4.dp)), + contentAlignment = Alignment.Center, + ) { + Icon(contentDescription = "taskGroup") { + iconKey(AllIconsKeys.Nodes.ConfigFolder, modifier = IconModifier.stroke(Color.White)) + } + } + Box( + Modifier.size(24.dp).background(backgroundColor, shape = RoundedCornerShape(4.dp)), + contentAlignment = Alignment.Center, + ) { + Icon(contentDescription = "taskGroup") { + iconKey(AllIconsKeys.Nodes.ConfigFolder, modifier = IconModifier.stroke(Color.White)) + badge(Color.Red, Circle) + } + } + Box(Modifier.size(24.dp), contentAlignment = Alignment.Center) { + Icon(contentDescription = "taskGroup") { + iconKey(AllIconsKeys.Nodes.ConfigFolder, modifier = IconModifier.size(20.dp)) + } + } + } + } + Column { var checked by remember { mutableStateOf(true) } @@ -137,11 +192,11 @@ public fun Icons(modifier: Modifier = Modifier) { modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp), ) { - Image(ShowcaseIcons.jewelLogo, contentDescription = "Jewel Logo", modifier = Modifier.size(96.dp)) + Image(ShowcaseIcons.jewelLogoKey, contentDescription = "Jewel Logo", modifier = Modifier.size(96.dp)) // Note: this currently looks identical to the hint-less image due to JEWEL-971 Image( - iconKey = ShowcaseIcons.jewelLogo, + iconKey = ShowcaseIcons.jewelLogoKey, contentDescription = "Jewel Logo with hint", hints = arrayOf(Stroke(Color.Red)), modifier = Modifier.size(96.dp), diff --git a/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/TitleBarView.kt b/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/TitleBarView.kt index d541dc423cf39..5dbed4561916e 100644 --- a/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/TitleBarView.kt +++ b/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/TitleBarView.kt @@ -12,6 +12,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import java.awt.Desktop import java.net.URI +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.design.px +import org.jetbrains.icons.icon +import org.jetbrains.icons.modifiers.IconModifier +import org.jetbrains.icons.modifiers.size import org.jetbrains.jewel.samples.showcase.ShowcaseIcons import org.jetbrains.jewel.samples.showcase.views.forCurrentOs import org.jetbrains.jewel.samples.standalone.IntUiThemes @@ -26,6 +31,7 @@ import org.jetbrains.jewel.window.DecoratedWindowScope import org.jetbrains.jewel.window.TitleBar import org.jetbrains.jewel.window.newFullscreenControls +@OptIn(ExperimentalIconsApi::class) @ExperimentalLayoutApi @Composable internal fun DecoratedWindowScope.TitleBarView() { @@ -117,7 +123,9 @@ internal fun DecoratedWindowScope.TitleBarView() { ) IntUiThemes.Dark -> - Icon(key = ShowcaseIcons.themeDark, contentDescription = "Dark", hints = arrayOf(Size(20))) + Icon(icon { + icon(ShowcaseIcons.themeDark, modifier = IconModifier.size(20.px)) + }, contentDescription = "Dark") IntUiThemes.System -> Icon( diff --git a/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/WelcomeView.kt b/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/WelcomeView.kt index 327d66f760cc2..c0ae8828df6e6 100644 --- a/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/WelcomeView.kt +++ b/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/WelcomeView.kt @@ -41,7 +41,7 @@ internal fun WelcomeView() { .semantics { isTraversalGroup = true }, verticalArrangement = Arrangement.spacedBy(24.dp), ) { - Icon(key = ShowcaseIcons.jewelLogo, contentDescription = null, modifier = Modifier.size(200.dp)) + Icon(key = ShowcaseIcons.jewelLogoKey, contentDescription = null, modifier = Modifier.size(200.dp)) Text("Meet Jewel", style = JewelTheme.typography.h1TextStyle) @@ -52,7 +52,7 @@ internal fun WelcomeView() { horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp), ) { - ThemeSelectionChip(IntUiThemes.Dark, "Dark", ShowcaseIcons.themeDark) + ThemeSelectionChip(IntUiThemes.Dark, "Dark", ShowcaseIcons.themeDarkKey) ThemeSelectionChip(IntUiThemes.Light, "Light", ShowcaseIcons.themeLight) diff --git a/platform/jewel/settings.gradle.kts b/platform/jewel/settings.gradle.kts index 2931a57bff13f..3b4249154c4b4 100644 --- a/platform/jewel/settings.gradle.kts +++ b/platform/jewel/settings.gradle.kts @@ -51,8 +51,18 @@ include( ":samples:standalone", ":ui", ":ui-tests", + ":jb-icons-api", + ":jb-icons-api-rendering", + ":jb-icons-api-rendering-lowlevel", + ":jb-icons-impl", ) +project(":jb-icons-api").projectDir = file("../icons-api") +project(":jb-icons-api-rendering").projectDir = file("../icons-api/rendering") +project(":jb-icons-api-rendering-lowlevel").projectDir = file("../icons-api/rendering/lowlevel") +project(":jb-icons-impl").projectDir = file("../icons-impl") + + develocity { buildScan { publishing.onlyIf { System.getenv("CI") == "true" } diff --git a/platform/jewel/ui/BUILD.bazel b/platform/jewel/ui/BUILD.bazel index e49e1987ff17b..a2055e77ab096 100644 --- a/platform/jewel/ui/BUILD.bazel +++ b/platform/jewel/ui/BUILD.bazel @@ -9,6 +9,8 @@ create_kotlinc_options( "androidx.compose.foundation.ExperimentalFoundationApi", "org.jetbrains.jewel.foundation.ExperimentalJewelApi", "org.jetbrains.jewel.foundation.InternalJewelApi", + "org.jetbrains.icons.api.InternalIconsApi", + "org.jetbrains.icons.api.ExperimentalIconsApi", ], x_context_parameters = True, x_explicit_api_mode = "strict" @@ -43,12 +45,18 @@ jvm_library( "@lib//:platform-jewel-ui-org-jetbrains-compose-components-components-resources-desktop", "//libraries/compose-foundation-desktop", "//libraries/compose-runtime-desktop", + "//platform/icons-api", + "//platform/icons-api/rendering", + "//platform/icons-api/rendering/lowlevel", + "//platform/icons-impl", ], exports = [ "//platform/jewel/foundation", "@lib//:platform-jewel-ui-org-jetbrains-compose-components-components-resources", "@lib//:platform-jewel-ui-org-jetbrains-compose-components-components-resources-desktop", "//libraries/compose-foundation-desktop", + "//platform/icons-api", + "//platform/icons-api/rendering", ], plugins = ["@lib//:compose-plugin"] ) @@ -77,6 +85,10 @@ jvm_library( "//libraries/compose-runtime-desktop:compose-runtime-desktop_test_lib", "//libraries/junit4", "//libraries/junit4:junit4_test_lib", + "//platform/icons-api", + "//platform/icons-api/rendering", + "//platform/icons-api/rendering/lowlevel", + "//platform/icons-impl", "//libraries/compose-foundation-desktop-junit", "//libraries/compose-foundation-desktop-junit:compose-foundation-desktop-junit_test_lib", ], @@ -87,6 +99,8 @@ jvm_library( "@lib//:platform-jewel-ui-org-jetbrains-compose-components-components-resources-desktop", "//libraries/compose-foundation-desktop", "//libraries/compose-foundation-desktop:compose-foundation-desktop_test_lib", + "//platform/icons-api", + "//platform/icons-api/rendering", ], plugins = ["@lib//:compose-plugin"] ) @@ -99,4 +113,4 @@ jps_test( name = "ui_test", runtime_deps = [":ui_test_lib"] ) -### auto-generated section `test intellij.platform.jewel.ui` end \ No newline at end of file +### auto-generated section `test intellij.platform.jewel.ui` end diff --git a/platform/jewel/ui/api-dump-experimental.txt b/platform/jewel/ui/api-dump-experimental.txt index 0529b6ab214f5..92d5c7a262891 100644 --- a/platform/jewel/ui/api-dump-experimental.txt +++ b/platform/jewel/ui/api-dump-experimental.txt @@ -96,6 +96,14 @@ f:org.jetbrains.jewel.ui.component.search.SpeedSearchableTreeKt - *sf:SpeedSearchableTree(org.jetbrains.jewel.ui.component.SpeedSearchScope,org.jetbrains.jewel.foundation.lazy.tree.Tree,kotlin.jvm.functions.Function1,androidx.compose.ui.Modifier,org.jetbrains.jewel.foundation.lazy.tree.TreeState,kotlin.jvm.functions.Function1,kotlin.jvm.functions.Function1,kotlin.jvm.functions.Function1,org.jetbrains.jewel.foundation.lazy.tree.KeyActions,org.jetbrains.jewel.ui.component.styling.LazyTreeStyle,kotlinx.coroutines.CoroutineDispatcher,kotlin.jvm.functions.Function4,androidx.compose.runtime.Composer,I,I,I):V f:org.jetbrains.jewel.ui.component.styling.IconButtonStylingKt - *sf:getLocalTransparentIconButtonStyle():androidx.compose.runtime.ProvidableCompositionLocal +f:org.jetbrains.jewel.ui.icon.IconUtilsKt +- *sf:tintColor-4MyTwCo(org.jetbrains.icons.api.modifiers.IconModifier,J,I):org.jetbrains.icons.api.modifiers.IconModifier +- *bs:tintColor-4MyTwCo$default(org.jetbrains.icons.api.modifiers.IconModifier,J,I,I,java.lang.Object):org.jetbrains.icons.api.modifiers.IconModifier +- *sf:toCompose(org.jetbrains.icons.api.design.BlendMode):I +- *sf:toCompose(org.jetbrains.icons.api.design.Color):J +- *sf:toCompose(org.jetbrains.icons.api.filters.ColorFilter):androidx.compose.ui.graphics.ColorFilter +- *sf:toIconsBlendMode-s9anfk8(I):org.jetbrains.icons.api.design.BlendMode +- *sf:toIconsColor-8_81llA(J):org.jetbrains.icons.api.design.RGBA *f:org.jetbrains.jewel.ui.painter.hints.EmbeddedToInlineCssStyleSvgPatchHint - org.jetbrains.jewel.ui.painter.PainterSvgPatchHint - sf:$stable:I diff --git a/platform/jewel/ui/api-dump.txt b/platform/jewel/ui/api-dump.txt index e7d09117cd855..450312e1eabd7 100644 --- a/platform/jewel/ui/api-dump.txt +++ b/platform/jewel/ui/api-dump.txt @@ -381,6 +381,8 @@ f:org.jetbrains.jewel.ui.component.IconButtonState$Companion - bs:of-3OtLUoY$default(org.jetbrains.jewel.ui.component.IconButtonState$Companion,Z,Z,Z,Z,Z,I,java.lang.Object):J f:org.jetbrains.jewel.ui.component.IconKt - sf:Icon(androidx.compose.ui.graphics.painter.Painter,java.lang.String,androidx.compose.ui.graphics.ColorFilter,androidx.compose.ui.Modifier,androidx.compose.runtime.Composer,I,I):V +- sf:Icon(java.lang.String,androidx.compose.ui.Modifier,org.jetbrains.icons.rendering.LoadingStrategy,kotlin.jvm.functions.Function1,androidx.compose.runtime.Composer,I,I):V +- sf:Icon(org.jetbrains.icons.Icon,java.lang.String,androidx.compose.ui.Modifier,org.jetbrains.icons.rendering.LoadingStrategy,androidx.compose.runtime.Composer,I,I):V - sf:Icon(org.jetbrains.jewel.ui.icon.IconKey,java.lang.String,androidx.compose.ui.Modifier,java.lang.Class,androidx.compose.ui.graphics.ColorFilter,org.jetbrains.jewel.ui.painter.PainterHint,androidx.compose.runtime.Composer,I,I):V - sf:Icon(org.jetbrains.jewel.ui.icon.IconKey,java.lang.String,androidx.compose.ui.Modifier,java.lang.Class,androidx.compose.ui.graphics.ColorFilter,org.jetbrains.jewel.ui.painter.PainterHint[],androidx.compose.runtime.Composer,I,I):V - sf:Icon-FHprtrg(org.jetbrains.jewel.ui.icon.IconKey,java.lang.String,androidx.compose.ui.Modifier,java.lang.Class,J,org.jetbrains.jewel.ui.painter.PainterHint,androidx.compose.runtime.Composer,I,I):V @@ -2360,6 +2362,10 @@ f:org.jetbrains.jewel.ui.graphics.CssLinearGradientBrushKt org.jetbrains.jewel.ui.icon.IconKey - a:getIconClass():java.lang.Class - a:path(Z):java.lang.String +f:org.jetbrains.jewel.ui.icon.IconKeyKt +- sf:iconKey(org.jetbrains.icons.design.IconDesigner,org.jetbrains.jewel.ui.icon.IconKey,org.jetbrains.icons.modifiers.IconModifier):V +- bs:iconKey$default(org.jetbrains.icons.design.IconDesigner,org.jetbrains.jewel.ui.icon.IconKey,org.jetbrains.icons.modifiers.IconModifier,I,java.lang.Object):V +f:org.jetbrains.jewel.ui.icon.IconUtilsKt f:org.jetbrains.jewel.ui.icon.IntelliJIconKey - org.jetbrains.jewel.ui.icon.IconKey - sf:$stable:I diff --git a/platform/jewel/ui/build.gradle.kts b/platform/jewel/ui/build.gradle.kts index 0c10b7c6a557b..da203d2afcfaf 100644 --- a/platform/jewel/ui/build.gradle.kts +++ b/platform/jewel/ui/build.gradle.kts @@ -13,6 +13,10 @@ plugins { dependencies { api(projects.foundation) + api(project(":jb-icons-api")) + api(project(":jb-icons-api-rendering")) + api(project(":jb-icons-api-rendering-lowlevel")) + api(project(":jb-icons-impl")) implementation(compose.components.resources) testImplementation(compose.desktop.uiTestJUnit4) testImplementation(compose.desktop.currentOs) { exclude(group = "org.jetbrains.compose.material") } diff --git a/platform/jewel/ui/exposed-third-party-api.txt b/platform/jewel/ui/exposed-third-party-api.txt index cbaeb3beca2d6..e855ffd4be5ac 100644 --- a/platform/jewel/ui/exposed-third-party-api.txt +++ b/platform/jewel/ui/exposed-third-party-api.txt @@ -1,4 +1,5 @@ androidx/compose/** kotlin/jvm/internal/DefaultConstructorMarker kotlin/ranges/ClosedFloatingPointRange +org/jetbrains/icons/api/** org/w3c/dom/Element diff --git a/platform/jewel/ui/intellij.platform.jewel.ui.iml b/platform/jewel/ui/intellij.platform.jewel.ui.iml index d34fb87e2ae61..775be879190ff 100644 --- a/platform/jewel/ui/intellij.platform.jewel.ui.iml +++ b/platform/jewel/ui/intellij.platform.jewel.ui.iml @@ -4,7 +4,7 @@ - @@ -82,6 +82,10 @@ + + + + \ No newline at end of file diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Icon.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Icon.kt index 0355422a62f19..4bd76d72c8248 100644 --- a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Icon.kt +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Icon.kt @@ -7,7 +7,11 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.key +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.paint import androidx.compose.ui.geometry.Size @@ -31,9 +35,17 @@ import org.jetbrains.compose.resources.ExperimentalResourceApi import org.jetbrains.compose.resources.decodeToImageBitmap import org.jetbrains.compose.resources.decodeToImageVector import org.jetbrains.compose.resources.decodeToSvgPainter +import org.jetbrains.icons.Icon +import org.jetbrains.icons.design.IconDesigner +import org.jetbrains.icons.icon +import org.jetbrains.icons.rendering.IconRendererManager +import org.jetbrains.icons.rendering.LoadingStrategy +import org.jetbrains.icons.rendering.createRenderer +import org.jetbrains.icons.impl.rendering.DefaultImageModifiers import org.jetbrains.jewel.foundation.modifier.thenIf import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.ui.icon.IconKey +import org.jetbrains.jewel.ui.icon.RendererBasedIconPainter import org.jetbrains.jewel.ui.icon.newUiChecker import org.jetbrains.jewel.ui.painter.PainterHint import org.jetbrains.jewel.ui.painter.rememberResourcePainterProvider @@ -76,11 +88,51 @@ public fun Icon( * always be provided unless this icon is used for decorative purposes, and does not represent a meaningful action * that a user can take. * @param modifier optional [Modifier] for this Icon. - * @param iconClass The class to use for resolving the icon resource. Defaults to `key.iconClass`. - * @param tint tint to be applied to the icon. If [Color.Unspecified] is provided, then no tint is applied. - * @param hint [PainterHint] to be passed to the painter. + * @param loadingStrategy specifies if the function should block the thread while loading the icon or show placeholder + * or render blank instead. */ -@Suppress("ComposableParamOrder") // To fix in JEWEL-929 +@Composable +public fun Icon( + icon: Icon, + contentDescription: String?, + modifier: Modifier = Modifier, + loadingStrategy: LoadingStrategy = LoadingStrategy.BlockThread +) { + val scope = rememberCoroutineScope() + val scalingContext = RendererBasedIconPainter.inferScalingContext() + var updateIndex by remember { mutableStateOf(0) } + val context = IconRendererManager.createRenderingContext( + updateFlow = IconRendererManager.createUpdateFlow(scope) { update -> + updateIndex = update + }, + defaultImageModifiers = DefaultImageModifiers( + isDark = JewelTheme.isDark + ) + ) + val renderer = remember(icon) { + icon.createRenderer(context) + } + val painter = remember(icon, renderer) { + RendererBasedIconPainter( + renderer, + scaling = scalingContext + ) + } + key(updateIndex) { + Icon(painter = painter, contentDescription = contentDescription, modifier = modifier) + } +} + +@Composable +public fun Icon( + contentDescription: String?, + modifier: Modifier = Modifier, + loadingStrategy: LoadingStrategy = LoadingStrategy.BlockThread, + designer: IconDesigner.() -> Unit +) { + Icon(icon(designer), contentDescription, modifier, loadingStrategy) +} + @Composable public fun Icon( key: IconKey, @@ -298,8 +350,8 @@ private object ResourceLoader private fun readResourceBytes(resourcePath: String) = checkNotNull(ResourceLoader.javaClass.classLoader.getResourceAsStream(resourcePath)) { - "Could not load resource $resourcePath: it does not exist or can't be read." - } + "Could not load resource $resourcePath: it does not exist or can't be read." + } .readAllBytes() private fun Modifier.defaultSizeFor(painter: Painter) = diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeBitmapImageResource.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeBitmapImageResource.kt new file mode 100644 index 0000000000000..2a4c56b316e3a --- /dev/null +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeBitmapImageResource.kt @@ -0,0 +1,130 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.jewel.ui.icon + +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.asComposeImageBitmap +import androidx.compose.ui.graphics.asSkiaBitmap +import org.jetbrains.icons.rendering.BitmapImageResource +import org.jetbrains.icons.rendering.Bounds +import org.jetbrains.icons.rendering.lowlevel.GPUImageResourceHolder +import org.jetbrains.icons.rendering.ImageScale +import org.jetbrains.icons.rendering.RescalableImageResource +import org.jetbrains.skia.Bitmap +import org.jetbrains.skia.ColorAlphaType +import org.jetbrains.skia.ImageInfo +import org.jetbrains.icons.impl.rendering.CachedGPUImageResourceHolder +import org.jetbrains.skia.ColorType +import org.jetbrains.skia.impl.BufferUtil + +internal class ComposeBitmapImageResource( + public val imageBitmap: ImageBitmap +): BitmapImageResource, CachedGPUImageResourceHolder() { + override fun getRGBPixels(): IntArray { + val skia = imageBitmap.asSkiaBitmap() + val pixelsNativePointer = skia.peekPixels()!!.addr + val pixelsBuffer = BufferUtil.getByteBufferFromPointer(pixelsNativePointer, skia.rowBytes * skia.height) + return pixelsBuffer.asIntBuffer().array() + } + + override fun readPrefetchedPixel(pixels: IntArray, x: Int, y: Int): Int? { + return pixels.getOrNull(y * imageBitmap.width + x) + } + + override fun getBandOffsetsToSRGB(): IntArray { + val skia = imageBitmap.asSkiaBitmap() + return when (skia.colorInfo.colorType) { + ColorType.RGB_888X -> intArrayOf(0, 1, 2, 3) + ColorType.BGRA_8888 -> intArrayOf(2, 1, 0, 3) + else -> throw UnsupportedOperationException("unsupported color type ${skia.colorInfo.colorType}") + } + } + + override val width: Int = imageBitmap.width + override val height: Int = imageBitmap.height +} + +internal fun BitmapImageResource.composeBitmap(): ImageBitmap { + if (this is ComposeBitmapImageResource) return imageBitmap + val cache = (this as? GPUImageResourceHolder) + return cache?.getOrGenerateBitmap(ImageBitmap::class) { + composeBitmapWithoutCaching().second + } ?: composeBitmapWithoutCaching().second +} + +internal fun RescalableImageResource.composeBitmap(scale: ImageScale): ImageBitmap { + val cache = (this as? GPUImageResourceHolder) + val cached = cache?.getOrGenerateBitmap(SingleBitmapCache::class) { SingleBitmapCache() } + if (cached != null) { + return cached.getOrPut(this, scale) + } else { + return scale(scale).composeBitmap() + } +} + +private class SingleBitmapCache { + private var lastBitmap: CachedBitmap? = null + + private class CachedBitmap( + val dimensions: Bounds, + val composeBitmap: ImageBitmap, + val bitmap: Bitmap + ) + + fun getOrPut(image: RescalableImageResource, scale: ImageScale): ImageBitmap { + val last = lastBitmap + val expectedDimensions = image.calculateExpectedDimensions(scale) + if (last == null) { + return createNewBitmap(image, scale, expectedDimensions) + } else { + if (last.dimensions == expectedDimensions) { + return last.composeBitmap + } else if (last.dimensions.canFit(expectedDimensions)) { + last.bitmap.setPixelsFrom(image.scale(scale)) + return last.composeBitmap + } else { + return createNewBitmap(image, scale, expectedDimensions) + } + } + } + + private fun createNewBitmap(image: RescalableImageResource, imageScale: ImageScale, dimensions: Bounds): ImageBitmap { + val (skia, compose) = image.scale(imageScale).composeBitmapWithoutCaching() + val new = CachedBitmap( + dimensions, + compose, + skia + ) + lastBitmap = new + return new.composeBitmap + } +} + +private fun BitmapImageResource.composeBitmapWithoutCaching(): Pair { + val bitmap = Bitmap() + bitmap.allocPixels(ImageInfo.makeS32(width, height, ColorAlphaType.UNPREMUL)) + bitmap.setPixelsFrom(this) + return bitmap to bitmap.asComposeImageBitmap() +} + +private fun Bitmap.setPixelsFrom(image: BitmapImageResource) { + val bytesPerPixel = 4 + val pixels = ByteArray(width * height * bytesPerPixel) + val prefetchedPixels = image.getRGBPixels() + + var k = 0 + for (y in 0 until height) { + for (x in 0 until width) { + val argb = image.readPrefetchedPixel(prefetchedPixels, x, y) ?: 0 + val a = (argb shr 24) and 0xff + val r = (argb shr 16) and 0xff + val g = (argb shr 8) and 0xff + val b = (argb shr 0) and 0xff + pixels[k++] = b.toByte() + pixels[k++] = g.toByte() + pixels[k++] = r.toByte() + pixels[k++] = a.toByte() + } + } + + installPixels(pixels) +} diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeImageResourceProvider.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeImageResourceProvider.kt new file mode 100644 index 0000000000000..31cd9c600c37d --- /dev/null +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeImageResourceProvider.kt @@ -0,0 +1,105 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.jewel.ui.icon + +import androidx.compose.ui.graphics.decodeToImageBitmap +import androidx.compose.ui.unit.Density +import java.io.ByteArrayInputStream +import java.io.InputStream +import javax.xml.XMLConstants +import javax.xml.parsers.DocumentBuilderFactory +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.compose.resources.decodeToSvgPainter +import org.jetbrains.icons.design.Color +import org.jetbrains.icons.modifiers.svgPatcher +import org.jetbrains.icons.patchers.SvgPatchOperation +import org.jetbrains.icons.patchers.SvgPatcher +import org.jetbrains.icons.patchers.combineWith +import org.jetbrains.icons.rendering.ImageModifiers +import org.jetbrains.icons.rendering.ImageResource +import org.jetbrains.icons.rendering.ImageResourceLoader +import org.jetbrains.icons.rendering.ImageResourceProvider +import org.jetbrains.icons.impl.rendering.DefaultImageModifiers +import org.jetbrains.jewel.foundation.InternalJewelApi +import org.jetbrains.jewel.ui.painter.writeToString +import org.w3c.dom.Element + +@InternalJewelApi +@ApiStatus.Internal +public class ComposeImageResourceProvider : ImageResourceProvider { + override fun loadImage(loader: ImageResourceLoader, imageModifiers: ImageModifiers?): ImageResource { + // TODO Support image modifiers + if (loader is PathImageResourceLoader) { + val extension = loader.path.substringAfterLast(".").lowercase() + val data = loader.loadData() + val stream = ByteArrayInputStream(data) + return when (extension) { + "svg" -> ComposePainterImageResource(patchSvg(imageModifiers, stream).decodeToSvgPainter(Density(1f)), imageModifiers) + // "xml" -> loader.loadData().decodeToImageVector() + else -> ComposeBitmapImageResource(loader.loadData().decodeToImageBitmap()) + } + } else { + error("Unsupported loader: $loader") + } + } +} + +private val documentBuilderFactory = + DocumentBuilderFactory.newDefaultInstance().apply { setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true) } + +private fun patchSvg(modifiers: ImageModifiers?, inputStream: InputStream): ByteArray { +val builder = documentBuilderFactory.newDocumentBuilder() + val document = builder.parse(inputStream) + + val knownModifiers = modifiers as? DefaultImageModifiers + val patcher = knownModifiers?.stroke?.let { stroke -> + svgPatcher { + replace("fill", Color.Transparent.toHex()) + replace("stroke", stroke.toHex()) + } + } combineWith modifiers?.svgPatcher + patcher?.patch(document.documentElement) + println("New SVG:") + println(document.writeToString()) + return document.writeToString().toByteArray() +} + +private fun SvgPatcher.patch(element: Element) { + for (operation in operations) { + when (operation.operation) { + SvgPatchOperation.Operation.Add -> { + if (!element.hasAttribute(operation.attributeName)) { + element.setAttribute(operation.attributeName, operation.value!!) + } + } + SvgPatchOperation.Operation.Replace -> { + if (operation.conditional) { + val matches = element.getAttribute(operation.attributeName) == operation.expectedValue + if (matches == !operation.negatedCondition) { + element.setAttribute(operation.attributeName, operation.value!!) + } + } else if (element.hasAttribute(operation.attributeName)) { + element.setAttribute(operation.attributeName, operation.value!!) + } + } + SvgPatchOperation.Operation.Remove -> { + if (operation.conditional) { + val matches = element.getAttribute(operation.attributeName) == operation.expectedValue + if (matches == !operation.negatedCondition) { + element.removeAttribute(operation.attributeName) + } + } else { + element.removeAttribute(operation.attributeName) + } + } + SvgPatchOperation.Operation.Set -> element.setAttribute(operation.attributeName, operation.value!!) + } + } + val nodes = element.childNodes + val length = nodes.length + for (i in 0 until length) { + val item = nodes.item(i) + if (item is Element) { + patch(item) + } + } +} diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposePainterImageResource.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposePainterImageResource.kt new file mode 100644 index 0000000000000..9dfba9e281591 --- /dev/null +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposePainterImageResource.kt @@ -0,0 +1,15 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.jewel.ui.icon + +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.util.fastRoundToInt +import org.jetbrains.icons.rendering.ImageModifiers +import org.jetbrains.icons.rendering.ImageResource + +internal class ComposePainterImageResource( + val painter: Painter, + val modifiers: ImageModifiers? +): ImageResource { + override val width: Int = painter.intrinsicSize.width.fastRoundToInt() + override val height: Int = painter.intrinsicSize.height.fastRoundToInt() +} diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposePaintingApi.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposePaintingApi.kt new file mode 100644 index 0000000000000..c0db6373af338 --- /dev/null +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposePaintingApi.kt @@ -0,0 +1,231 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.jewel.ui.icon + +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.FilterQuality +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.DrawScope.Companion.DefaultBlendMode +import androidx.compose.ui.graphics.drawscope.Fill +import androidx.compose.ui.graphics.drawscope.translate +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntSize +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.icons.filters.ColorFilter +import org.jetbrains.icons.rendering.BitmapImageResource +import org.jetbrains.icons.rendering.Bounds +import org.jetbrains.icons.rendering.FitAreaScale +import org.jetbrains.icons.rendering.ImageResource +import org.jetbrains.icons.rendering.RescalableImageResource +import org.jetbrains.icons.rendering.PaintingApi +import org.jetbrains.jewel.foundation.InternalJewelApi +import kotlin.math.roundToInt +import org.jetbrains.icons.design.Color +import org.jetbrains.icons.rendering.DrawMode +import org.jetbrains.icons.rendering.ScalingContext +import org.jetbrains.jewel.foundation.GenerateDataFunctions + +@InternalJewelApi +@ApiStatus.Internal +public class ComposePaintingApi( + public val drawScope: DrawScope, + private val customBounds: Bounds? = null, + private val overrideColorFilter: ColorFilter? = null, + override val scaling: ScalingContext = ComposeScalingContext(drawScope.drawContext.density.density) +): PaintingApi { + private var usedBounds: Bounds? = null + + override val bounds: Bounds = customBounds ?: Bounds( + 0, + 0, + drawScope.size.width.roundToInt(), + drawScope.size.height.roundToInt() + ) + + override fun withCustomContext( + bounds: Bounds, + overrideColorFilter: ColorFilter? + ): PaintingApi { + return ComposePaintingApi(drawScope, bounds, overrideColorFilter ?: this.overrideColorFilter) + } + + override fun drawCircle(color: Color, x: Int, y: Int, radius: Float, alpha: Float, mode: DrawMode) { + val style = if (mode == DrawMode.Stroke) { + Fill + } else Fill // TODO Support strokes + val blendMode = if (mode == DrawMode.Clear) { + BlendMode.Clear + } else DefaultBlendMode + drawScope.drawCircle( + color.toCompose(), + radius, + Offset(x.toFloat(), y.toFloat()), + alpha, + style = style, + blendMode = blendMode + ) + } + + override fun drawRect(color: Color, x: Int, y: Int, width: Int, height: Int, alpha: Float, mode: DrawMode) { + val style = if (mode == DrawMode.Stroke) { + Fill + } else Fill // TODO Support strokes + val blendMode = if (mode == DrawMode.Clear) { + BlendMode.Clear + } else DefaultBlendMode + drawScope.drawRect( + color.toCompose(), + Offset(x.toFloat(), y.toFloat()), + Size(width.toFloat(), height.toFloat()), + alpha, + style = style, + blendMode = blendMode + ) + } + + override fun drawImage( + image: ImageResource, + x: Int, + y: Int, + width: Int?, + height: Int?, + srcX: Int, + srcY: Int, + srcWidth: Int?, + srcHeight: Int?, + alpha: Float, + colorFilter: ColorFilter? + ) { + when (image) { + is BitmapImageResource -> { + drawImage(image, x, y, width, height, srcX, srcY, srcWidth, srcHeight, alpha, colorFilter) + } + is RescalableImageResource -> { + drawImage(image, x, y, width, height, srcX, srcY, srcWidth, srcHeight, alpha, colorFilter) + } + is ComposePainterImageResource -> { + drawImage(image, x, y, width, height, srcX, srcY, srcWidth, srcHeight, alpha, colorFilter) + } + } + } + + override fun getUsedBounds(): Bounds = usedBounds ?: bounds + + private fun drawImage( + image: BitmapImageResource, + x: Int, + y: Int, + width: Int?, + height: Int?, + srcX: Int, + srcY: Int, + srcWidth: Int?, + srcHeight: Int?, + alpha: Float, + colorFilter: ColorFilter? + ) { + drawComposeImage(image.composeBitmap(), x, y, width, height, srcX, srcY, srcWidth, srcHeight, alpha, colorFilter) + } + + private fun drawImage( + image: RescalableImageResource, + x: Int, + y: Int, + width: Int?, + height: Int?, + srcX: Int, + srcY: Int, + srcWidth: Int?, + srcHeight: Int?, + alpha: Float, + colorFilter: ColorFilter? + ) { + val scaledImage = image.composeBitmap(FitAreaScale(width ?: bounds.width, height ?: bounds.width)) + drawComposeImage(scaledImage, x, y, width, height, srcX, srcY, srcWidth, srcHeight, alpha, colorFilter) + } + + private fun drawImage( + image: ComposePainterImageResource, + x: Int, + y: Int, + width: Int?, + height: Int?, + srcX: Int, + srcY: Int, + srcWidth: Int?, + srcHeight: Int?, + alpha: Float, + colorFilter: ColorFilter? + ) { + if (image.width == 0 || image.height == 0) return + val targetSize = IntSize(width ?: bounds.width, height ?: bounds.height) + drawScope.translate(x.toFloat(), y.toFloat()) { + with(image.painter) { + draw( + Size( + targetSize.width.toFloat(), + targetSize.height.toFloat() + ), + alpha, + convertColorFilter(colorFilter ?: image.modifiers?.colorFilter) + ) + } + } + } + + private fun drawComposeImage( + image: ImageBitmap, + x: Int, + y: Int, + width: Int?, + height: Int?, + srcX: Int, + srcY: Int, + srcWidth: Int?, + srcHeight: Int?, + alpha: Float, + colorFilter: ColorFilter? = null + ) { + if (image.width == 0 || image.height == 0) return + val targetSize = IntSize(width ?: image.width, height ?: image.height) + if (targetSize.width == 0 || targetSize.height == 0) return + drawScope.drawImage( + image, + IntOffset(srcX, srcY), + IntSize(srcWidth ?: image.width, srcHeight ?: image.height), + dstOffset = IntOffset(x, y), + dstSize = targetSize, + alpha = alpha, + colorFilter = convertColorFilter(colorFilter), + filterQuality = FilterQuality.Low + ) + } + + private fun convertColorFilter(colorFilter: ColorFilter?): androidx.compose.ui.graphics.ColorFilter? { + return (overrideColorFilter ?: colorFilter)?.toCompose() + } +} + +@GenerateDataFunctions +internal class ComposeScalingContext( + override val display: Float +) : ScalingContext { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ComposeScalingContext + + return display == other.display + } + + override fun hashCode(): Int { + return display.hashCode() + } + + override fun toString(): String { + return "ComposeScalingContext(display=$display)" + } +} diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/IconKey.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/IconKey.kt index 8aaa8bbc0ad2f..aaa19acdcf985 100644 --- a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/IconKey.kt +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/IconKey.kt @@ -1,5 +1,8 @@ package org.jetbrains.jewel.ui.icon +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.design.IconDesigner +import org.jetbrains.icons.modifiers.IconModifier import org.jetbrains.jewel.foundation.GenerateDataFunctions public interface IconKey { @@ -8,6 +11,11 @@ public interface IconKey { public fun path(isNewUi: Boolean): String } +@ExperimentalIconsApi +public fun IconDesigner.iconKey(iconKey: IconKey, modifier: IconModifier = IconModifier) { + image(iconKey.path(isNewUi = true), iconKey.iconClass.classLoader, modifier) +} + @GenerateDataFunctions public class PathIconKey(private val path: String, override val iconClass: Class<*>) : IconKey { override fun path(isNewUi: Boolean): String = path diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/IconUtils.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/IconUtils.kt new file mode 100644 index 0000000000000..7170adebd6411 --- /dev/null +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/IconUtils.kt @@ -0,0 +1,103 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.jewel.ui.icon + +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.icons.design.IconAlign +import org.jetbrains.icons.design.Shape +import org.jetbrains.icons.design.IconDesigner +import org.jetbrains.icons.design.IconUnit +import org.jetbrains.icons.design.RGBA +import org.jetbrains.icons.design.badge +import org.jetbrains.icons.design.dp +import org.jetbrains.icons.design.relativeTo +import org.jetbrains.icons.filters.ColorFilter +import org.jetbrains.icons.filters.TintColorFilter +import org.jetbrains.icons.modifiers.IconModifier +import org.jetbrains.icons.modifiers.size +import org.jetbrains.icons.modifiers.stroke +import org.jetbrains.icons.modifiers.tintColor +import org.jetbrains.jewel.foundation.ExperimentalJewelApi + +@ExperimentalJewelApi +@ApiStatus.Experimental +public fun IconModifier.tintColor(composeColor: Color, blendMode: BlendMode = BlendMode.SrcIn): IconModifier { + return tintColor(composeColor.toIconsColor(), blendMode.toIconsBlendMode()) +} + +@ExperimentalJewelApi +@ApiStatus.Experimental +public fun IconModifier.stroke(composeColor: Color): IconModifier { + return stroke(composeColor.toIconsColor()) +} + +@ExperimentalJewelApi +@ApiStatus.Experimental +public fun IconModifier.size(size: Dp): IconModifier { + return size(size.value.dp) +} + + +@ExperimentalJewelApi +@ApiStatus.Experimental +public fun IconDesigner.badge( + color: Color, + shape: Shape, + size: IconUnit = (3.5 * 2).dp relativeTo 20.dp, + align: IconAlign = IconAlign.TopRight, + cutout: IconUnit = 1.5.dp relativeTo 20.dp, + modifier: IconModifier = IconModifier, +) { + badge(color.toIconsColor(), shape, size, align, cutout, modifier) +} + +@ExperimentalJewelApi +@ApiStatus.Experimental +public fun ColorFilter.toCompose(): androidx.compose.ui.graphics.ColorFilter { + return when (this) { + is TintColorFilter -> androidx.compose.ui.graphics.ColorFilter.tint(color.toCompose(), blendMode.toCompose()) + } +} + +@ExperimentalJewelApi +@ApiStatus.Experimental +public fun org.jetbrains.icons.design.Color.toCompose(): Color { + return when (this) { + is RGBA -> Color(red, green, blue, alpha) + } +} + +@ExperimentalJewelApi +@ApiStatus.Experimental +public fun org.jetbrains.icons.design.BlendMode.toCompose(): BlendMode { + return when (this) { + org.jetbrains.icons.design.BlendMode.SrcIn -> BlendMode.SrcIn + org.jetbrains.icons.design.BlendMode.Color -> BlendMode.Color + org.jetbrains.icons.design.BlendMode.Hue -> BlendMode.Hue + org.jetbrains.icons.design.BlendMode.Luminosity -> BlendMode.Luminosity + org.jetbrains.icons.design.BlendMode.Saturation -> BlendMode.Saturation + org.jetbrains.icons.design.BlendMode.Multiply -> BlendMode.Multiply + } +} + +@ExperimentalJewelApi +@ApiStatus.Experimental +public fun BlendMode.toIconsBlendMode(): org.jetbrains.icons.design.BlendMode { + return when (this) { + BlendMode.SrcIn -> org.jetbrains.icons.design.BlendMode.SrcIn + BlendMode.Color -> org.jetbrains.icons.design.BlendMode.Color + BlendMode.Hue -> org.jetbrains.icons.design.BlendMode.Hue + BlendMode.Luminosity -> org.jetbrains.icons.design.BlendMode.Luminosity + BlendMode.Saturation -> org.jetbrains.icons.design.BlendMode.Saturation + BlendMode.Multiply -> org.jetbrains.icons.design.BlendMode.Multiply + else -> error("Unsupported Compose blend mode $this") + } +} + +@ExperimentalJewelApi +@ApiStatus.Experimental +public fun Color.toIconsColor(): RGBA { + return RGBA(red, green, blue, alpha) +} diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/RendererBasedIconPainter.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/RendererBasedIconPainter.kt new file mode 100644 index 0000000000000..53d0215460d91 --- /dev/null +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/RendererBasedIconPainter.kt @@ -0,0 +1,59 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.jewel.ui.icon + +import androidx.compose.runtime.Composable +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Paint +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.withSaveLayer +import androidx.compose.ui.platform.LocalDensity +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.icons.rendering.Dimensions +import org.jetbrains.icons.rendering.IconRenderer +import org.jetbrains.icons.rendering.ScalingContext +import org.jetbrains.jewel.foundation.InternalJewelApi + +@InternalJewelApi +@ApiStatus.Internal +public class RendererBasedIconPainter(private val iconRenderer: IconRenderer, private val scaling: ScalingContext) : Painter() { + override val intrinsicSize: Size = iconRenderer.calculateExpectedDimensions(scaling).toComposeSize() + + private var layerPaint: Paint? = null + private fun obtainPaint(): Paint { + var target = layerPaint + if (target == null) { + target = Paint() + layerPaint = target + } + return target + } + + override fun DrawScope.onDraw() { + val api = ComposePaintingApi(this, scaling = scaling) + val layerRect = Rect(Offset.Zero, Size(size.width, size.height)) + drawIntoCanvas { canvas -> + canvas.withSaveLayer(layerRect, obtainPaint()) { + iconRenderer.render(api) + } + } + } + + public companion object { + @Composable + @InternalJewelApi + @ApiStatus.Internal + public fun inferScalingContext(): ScalingContext { + return ComposeScalingContext( + LocalDensity.current.density + ) + } + } +} + +private fun Dimensions.toComposeSize(): Size { + return Size(width.toFloat(), height.toFloat()) +} diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ResourceImageIconLoader.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ResourceImageIconLoader.kt new file mode 100644 index 0000000000000..52c5a56c04796 --- /dev/null +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ResourceImageIconLoader.kt @@ -0,0 +1,15 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.jewel.ui.icon + +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.icons.rendering.ImageResourceLoader +import org.jetbrains.jewel.foundation.InternalJewelApi + +@InternalJewelApi +@ApiStatus.Internal +public class PathImageResourceLoader(public val path: String, public val classLoader: ClassLoader?) : ImageResourceLoader { + public fun loadData(): ByteArray { + if (classLoader != null) return classLoader.getResourceAsStream(path)!!.readBytes() + return ClassLoader.getSystemResourceAsStream(path)!!.readBytes() + } +} diff --git a/platform/platform-impl/bootstrap/BUILD.bazel b/platform/platform-impl/bootstrap/BUILD.bazel index 6cdd10ef73437..dec24e1789086 100644 --- a/platform/platform-impl/bootstrap/BUILD.bazel +++ b/platform/platform-impl/bootstrap/BUILD.bazel @@ -46,6 +46,8 @@ jvm_library( "//platform/eel-impl", "//platform/platform-impl/initial-config-import", "//platform/eel-provider", + "//platform/icons-impl", + "//platform/icons-impl/intellij", ] ) diff --git a/platform/platform-impl/bootstrap/intellij.platform.ide.bootstrap.iml b/platform/platform-impl/bootstrap/intellij.platform.ide.bootstrap.iml index f86862f009fc2..59decad7ed82e 100644 --- a/platform/platform-impl/bootstrap/intellij.platform.ide.bootstrap.iml +++ b/platform/platform-impl/bootstrap/intellij.platform.ide.bootstrap.iml @@ -46,5 +46,7 @@ + + \ No newline at end of file diff --git a/platform/platform-impl/bootstrap/src/com/intellij/platform/ide/bootstrap/ui.kt b/platform/platform-impl/bootstrap/src/com/intellij/platform/ide/bootstrap/ui.kt index 14d51c648b445..3c1bb38df94de 100644 --- a/platform/platform-impl/bootstrap/src/com/intellij/platform/ide/bootstrap/ui.kt +++ b/platform/platform-impl/bootstrap/src/com/intellij/platform/ide/bootstrap/ui.kt @@ -34,6 +34,12 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.jetbrains.annotations.VisibleForTesting +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.rendering.ImageResourceProvider +import org.jetbrains.icons.impl.intellij.IntelliJIconManager +import org.jetbrains.icons.impl.intellij.rendering.IntelliJIconRendererManager +import org.jetbrains.icons.impl.intellij.rendering.IntelliJImageResourceProvider +import org.jetbrains.icons.rendering.IconRendererManager import java.awt.Font import java.awt.GraphicsEnvironment import java.awt.Toolkit @@ -44,11 +50,16 @@ import javax.swing.RepaintManager import javax.swing.UIManager import kotlin.system.exitProcess +@OptIn(ExperimentalIconsApi::class) internal suspend fun initUi(initAwtToolkitJob: Job, isHeadless: Boolean, asyncScope: CoroutineScope) { // IdeaLaF uses AllIcons - icon manager must be activated if (!isHeadless) { span("icon manager activation") { - IconManager.activate(CoreIconManager()) + val iconManager = CoreIconManager() + IconManager.activate(iconManager) + } + span("new icon manager activation") { + IntelliJIconManager.activate() } } diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.ide.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.ide.xml index 4c4131e9934e4..236f7f0147690 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.ide.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.ide.xml @@ -131,6 +131,10 @@ + + + + diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.lang.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.lang.xml index 539f655e28e5a..6baf3aa3fb869 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.lang.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.lang.xml @@ -132,6 +132,10 @@ + + + + @@ -169,6 +173,8 @@ + + diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.minimal.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.minimal.xml index 15ac00f3a8694..3a9f674014ea4 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.minimal.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.minimal.xml @@ -133,6 +133,10 @@ + + + + @@ -170,6 +174,8 @@ + + diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.xml index 39e7a937950a1..09de132fcb264 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.xml @@ -134,6 +134,10 @@ + + + + @@ -171,6 +175,8 @@ + + diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.ide.common.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.ide.common.xml index 8fcdda615fbf0..fcb308d0c41ad 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.ide.common.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.ide.common.xml @@ -137,6 +137,10 @@ + + + + @@ -174,6 +178,8 @@ + + diff --git a/platform/util/BUILD.bazel b/platform/util/BUILD.bazel index ca4cd099714ca..8a216ced6e2e1 100644 --- a/platform/util/BUILD.bazel +++ b/platform/util/BUILD.bazel @@ -40,6 +40,9 @@ jvm_library( "//platform/util/coroutines", "//platform/util/multiplatform", "//platform/eel", + "//platform/icons-api", + "//platform/icons-api/rendering", + "//platform/icons-api/legacy-icon-support", ":platform_util_troveCompileOnly_provided", ], exports = [ @@ -47,6 +50,9 @@ jvm_library( "//platform/util-rt", "//platform/util/base", "//platform/util/multiplatform", + "//platform/icons-api", + "//platform/icons-api/rendering", + "//platform/icons-api/legacy-icon-support", ], runtime_deps = [ "//libraries/commons/io", diff --git a/platform/util/intellij.platform.util.iml b/platform/util/intellij.platform.util.iml index 29aa589b65012..78b686121859a 100644 --- a/platform/util/intellij.platform.util.iml +++ b/platform/util/intellij.platform.util.iml @@ -54,6 +54,9 @@ + + + diff --git a/platform/util/ui/src/com/intellij/ui/icons/CachedImageIcon.kt b/platform/util/ui/src/com/intellij/ui/icons/CachedImageIcon.kt index 5013348f72379..a349bcd48745f 100644 --- a/platform/util/ui/src/com/intellij/ui/icons/CachedImageIcon.kt +++ b/platform/util/ui/src/com/intellij/ui/icons/CachedImageIcon.kt @@ -172,7 +172,8 @@ open class CachedImageIcon private constructor( return (image as? JBHiDPIScaledImage)?.delegate ?: image } - internal fun resolveImage(scaleContext: ScaleContext?): Image? { + @Internal + fun resolveImage(scaleContext: ScaleContext?): Image? { val icon = if (scaleContext == null) resolveActualIcon() else resolveActualIcon(scaleContext) return if (icon is ScaledResultIcon) icon.image else null } diff --git a/platform/util/ui/src/com/intellij/ui/icons/ImageDataByPathLoader.kt b/platform/util/ui/src/com/intellij/ui/icons/ImageDataByPathLoader.kt index f213baabc0588..4775423176536 100644 --- a/platform/util/ui/src/com/intellij/ui/icons/ImageDataByPathLoader.kt +++ b/platform/util/ui/src/com/intellij/ui/icons/ImageDataByPathLoader.kt @@ -14,6 +14,21 @@ import java.net.URL import java.util.function.Supplier import javax.swing.Icon +@Internal +fun findIconLoaderByPath(path: String, classLoader: ClassLoader): ImageDataLoader { + val originalPath = normalizePath(path) + val patched = patchIconPath(originalPath, classLoader) + val effectivePath = patched?.first ?: originalPath + val effectiveClassLoader = patched?.second ?: classLoader + + return ImageDataByPathLoader.createLoader(originalPath = originalPath, + originalClassLoader = effectiveClassLoader, + patched = patched, + path = effectivePath, + classLoader = effectiveClassLoader + ) +} + @Internal fun findIconByPath( @NonNls path: String, @@ -62,14 +77,28 @@ internal class ImageDataByPathLoader private constructor(override val path: Stri private val classLoader: ClassLoader, private val original: ImageDataByPathLoader?) : ImageDataLoader { companion object { + internal fun createLoader(originalPath: @NonNls String, + originalClassLoader: ClassLoader, + patched: Pair?, + path: String, + classLoader: ClassLoader): ImageDataByPathLoader { + val originalLoader = ImageDataByPathLoader(path = originalPath, classLoader = originalClassLoader, original = null) + return if (patched == null) originalLoader else ImageDataByPathLoader(path = path, classLoader = classLoader, original = originalLoader) + } + internal fun createIcon(originalPath: @NonNls String, originalClassLoader: ClassLoader, patched: Pair?, path: String, classLoader: ClassLoader, toolTip: Supplier? = null): CachedImageIcon { - val originalLoader = ImageDataByPathLoader(path = originalPath, classLoader = originalClassLoader, original = null) - val loader = if (patched == null) originalLoader else ImageDataByPathLoader(path = path, classLoader = classLoader, original = originalLoader) + val loader = createLoader( + originalPath, + originalClassLoader, + patched, + path, + classLoader + ) return CachedImageIcon(loader = loader, toolTip = toolTip, originalLoader = loader.original ?: loader) } diff --git a/platform/util/ui/src/com/intellij/ui/icons/LegacyIcon.kt b/platform/util/ui/src/com/intellij/ui/icons/LegacyIcon.kt new file mode 100644 index 0000000000000..89fefa2a31b8e --- /dev/null +++ b/platform/util/ui/src/com/intellij/ui/icons/LegacyIcon.kt @@ -0,0 +1,35 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +/*package com.intellij.ui.icons + +import org.jetbrains.icons.api.IconIdentifier +import org.jetbrains.icons.api.customIconIdentifier +import java.util.UUID + +interface LegacySwingIcon : org.jetbrains.icons.api.Icon, javax.swing.Icon { + val swingIcon: javax.swing.Icon + + override fun paintIcon(c: java.awt.Component, g: java.awt.Graphics, x: Int, y: Int) { + swingIcon.paintIcon(c, g, x, y) + } + override fun getIconWidth(): Int = swingIcon.iconWidth + override fun getIconHeight(): Int = swingIcon.iconHeight +} + +interface LegacyIcon : LegacySwingIcon { + override val swingIcon: TIcon +} + +private class LegacyIconImpl( + override val identifier: IconIdentifier, + override val swingIcon: TIcon +): LegacyIcon + +fun TIcon.legacyIcon(): LegacyIcon { + val identifier = if (this is CachedImageIcon) { + customIconIdentifier("::legacy/CachedImageIcon/") + } else customIconIdentifier("::legacy/unknown/" + UUID.randomUUID().toString()) + return LegacyIconImpl( + identifier, + this + ) +}*/ \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.compose/BUILD.bazel b/plugins/devkit/intellij.devkit.compose/BUILD.bazel index 4d4ffb192adeb..2cd7f63758021 100644 --- a/plugins/devkit/intellij.devkit.compose/BUILD.bazel +++ b/plugins/devkit/intellij.devkit.compose/BUILD.bazel @@ -7,6 +7,7 @@ create_kotlinc_options( opt_in = [ "androidx.compose.foundation.ExperimentalFoundationApi", "kotlinx.coroutines.ExperimentalCoroutinesApi", + "org.jetbrains.icons.api.ExperimentalIconsApi", ], plugin_options = ["plugin:androidx.compose.compiler.plugins.kotlin:generateFunctionKeyMetaAnnotations=true"] ) @@ -63,6 +64,11 @@ jvm_library( "//platform/util/jdom", "//platform/external-system-impl:externalSystem-impl", "//plugins/gradle", + "//platform/icons-api/legacy-icon-support", + "//platform/platform-impl/rpc", + "//fleet/util/core", + "//libraries/kotlinx/serialization/json", + "//libraries/kotlinx/serialization/core", ], plugins = ["@lib//:compose-plugin"] ) diff --git a/plugins/devkit/intellij.devkit.compose/intellij.devkit.compose.iml b/plugins/devkit/intellij.devkit.compose/intellij.devkit.compose.iml index 6d956224c0c3f..91ec40251c1b8 100644 --- a/plugins/devkit/intellij.devkit.compose/intellij.devkit.compose.iml +++ b/plugins/devkit/intellij.devkit.compose/intellij.devkit.compose.iml @@ -4,7 +4,7 @@ - @@ -68,5 +68,10 @@ + + + + + \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.compose/src/demo/ComponentShowcaseTab.kt b/plugins/devkit/intellij.devkit.compose/src/demo/ComponentShowcaseTab.kt index effb48ba9ca46..0c5fe5e1a4952 100644 --- a/plugins/devkit/intellij.devkit.compose/src/demo/ComponentShowcaseTab.kt +++ b/plugins/devkit/intellij.devkit.compose/src/demo/ComponentShowcaseTab.kt @@ -38,10 +38,14 @@ import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.onFirstVisible import androidx.compose.ui.unit.dp +import com.intellij.icons.AllIcons import com.intellij.ide.BrowserUtil import com.intellij.openapi.project.Project +import com.intellij.ui.IconDeferrer import com.intellij.ui.JBColor import com.intellij.util.ui.JBUI +import kotlinx.coroutines.delay +import org.jetbrains.icons.Icon import org.jetbrains.jewel.bridge.toComposeColor import org.jetbrains.jewel.foundation.LocalComponent import org.jetbrains.jewel.foundation.actionSystem.provideData @@ -457,7 +461,7 @@ private fun IconsShowcase() { } IconButton(onClick = {}, Modifier.size(24.dp)) { - Icon(key = AllIconsKeys.Actions.Close, contentDescription = "Close") + Icon(AllIconsKeys.Actions.Close, "Close") } IconActionButton( @@ -468,9 +472,25 @@ private fun IconsShowcase() { hints = arrayOf(Size(24)), tooltip = { Text("Hello there") }, ) + + Box { + Icon(AllIcons.General.OpenDisk as Icon, "Build Load Changes") + } + + Box { + Icon(deferedIcon as Icon, "Deferred Icon Sample") + } } } +private val deferedIcon = IconDeferrer.getInstance().deferAsync( + AllIcons.General.Print, + "KABOOM-DEF_ICON_TST" +) { + delay(10000) + AllIcons.General.GreenCheckmark +} + @Composable private fun RowScope.ColumnTwo(project: Project) { Column(Modifier.trackActivation().weight(1f), verticalArrangement = Arrangement.spacedBy(16.dp)) { diff --git a/plugins/devkit/intellij.devkit.compose/src/showcase/ComposePerformanceDemoAction.kt b/plugins/devkit/intellij.devkit.compose/src/showcase/ComposePerformanceDemoAction.kt index ac2735366f0b6..8a332f2da66bc 100644 --- a/plugins/devkit/intellij.devkit.compose/src/showcase/ComposePerformanceDemoAction.kt +++ b/plugins/devkit/intellij.devkit.compose/src/showcase/ComposePerformanceDemoAction.kt @@ -1,6 +1,7 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.devkit.compose.showcase +import androidx.compose.animation.core.EaseInOut import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.animateFloat @@ -8,7 +9,9 @@ import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.tween import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -18,8 +21,11 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.withFrameNanos +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color @@ -31,18 +37,26 @@ import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.intellij.icons.AllIcons import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.project.Project import com.intellij.openapi.ui.DialogWrapper +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.legacyIconSupport.toNewIcon import org.jetbrains.jewel.bridge.compose +import org.jetbrains.jewel.ui.component.Checkbox +import org.jetbrains.jewel.ui.component.Icon import org.jetbrains.jewel.ui.component.Slider import org.jetbrains.jewel.ui.component.Text +import org.jetbrains.jewel.ui.icon.IconKey +import org.jetbrains.jewel.ui.icons.AllIconsKeys import java.awt.BorderLayout import java.util.LinkedList import javax.swing.BoxLayout import javax.swing.ButtonGroup +import javax.swing.Icon import javax.swing.JComponent import javax.swing.JLabel import javax.swing.JPanel @@ -69,7 +83,7 @@ private class MyDialog(project: Project?, dialogTitle: String) : DialogWrapper(project, null, true, IdeModalityType.MODELESS, true) { val centerPanelWrapper = JPanel(BorderLayout()) - enum class TestCase { TextAnimation, Canvas } + enum class TestCase { TextAnimation, Canvas, Icons } enum class Mode { Swing, AWT } var mode = Mode.Swing @@ -94,17 +108,22 @@ private class MyDialog(project: Project?, dialogTitle: String) : ButtonGroup().let { group -> val textAnimationButton = JRadioButton("Text animation") val canvasButton = JRadioButton("Canvas") + val iconsButton = JRadioButton("Icons") group.add(textAnimationButton) group.add(canvasButton) + group.add(iconsButton) textAnimationButton.isSelected = testCase == TestCase.TextAnimation canvasButton.isSelected = testCase == TestCase.Canvas + iconsButton.isSelected = testCase == TestCase.Icons textAnimationButton.addActionListener { testCase = TestCase.TextAnimation; initCentralPanel() } canvasButton.addActionListener { testCase = TestCase.Canvas; initCentralPanel() } + iconsButton.addActionListener { testCase = TestCase.Icons; initCentralPanel() } controlPanel.add(canvasButton) controlPanel.add(textAnimationButton) + controlPanel.add(iconsButton) } controlPanel.add(JSeparator(JSeparator.VERTICAL)) @@ -139,6 +158,7 @@ private class MyDialog(project: Project?, dialogTitle: String) : val comp = when (testCase) { TestCase.TextAnimation -> createTextAnimationComponent() TestCase.Canvas -> createClockComponent() + TestCase.Icons -> createIconsComponent() } centerPanelWrapper.add(comp) @@ -149,6 +169,112 @@ private class MyDialog(project: Project?, dialogTitle: String) : } } +@OptIn(ExperimentalIconsApi::class) +private fun createIconsComponent(): JComponent { + return compose { + var minFps by remember { mutableStateOf(Int.MAX_VALUE) } + var maxFps by remember { mutableStateOf(Int.MIN_VALUE) } + val frameTimes = remember { LinkedList() } + SideEffect { + frameTimes.add(System.nanoTime()) + frameTimes.removeAll { it < System.nanoTime() - 1_000_000_000 } + } + + val transition = rememberInfiniteTransition("coso") + val iconSize by transition.animateFloat( + 30f, + 50f, + infiniteRepeatable(tween(durationMillis = 1000, easing = EaseInOut), repeatMode = RepeatMode.Reverse), + ) + + Column { + val fps = frameTimes.size + if (fps > maxFps) { + maxFps = fps + } + if (fps < minFps) { + minFps = fps + } + Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { + Text( + text = "FPS: $fps", + modifier = Modifier.padding(10.dp), + fontSize = 25.sp, + color = Color.Red + ) + Text( + text = "MIN: $minFps", + modifier = Modifier.padding(10.dp), + fontSize = 25.sp, + color = Color.Red + ) + Text( + text = "MAX: $maxFps", + modifier = Modifier.padding(10.dp), + fontSize = 25.sp, + color = Color.Red + ) + } + + var useComposeIcons by remember { mutableStateOf(false) } + Row { + Text("Use Compose Icons: ") + Checkbox(useComposeIcons, { + maxFps = Int.MIN_VALUE + minFps = Int.MAX_VALUE + useComposeIcons = it + }) + } + + Column(modifier = Modifier.fillMaxSize().clipToBounds(), verticalArrangement = Arrangement.Center) { + if (useComposeIcons) { + val icons = remember { + val icons = mutableListOf() + for (nested in AllIconsKeys::class.java.nestMembers) { + val fields = nested.declaredFields.filter { it.type.name.contains("IconKey") } + icons.addAll(fields.map { it.get(null) as IconKey }) + } + icons + } + + var i = 1 + for (x in 0..30) { + Row { + for (y in 0..30) { + Column(modifier = Modifier.size(55.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) { + val icon = icons[i++ % icons.size] + Icon(icon, "Balloon", modifier = Modifier.size(iconSize.dp).padding(10.dp)) + } + } + } + } + } else { + val icons = remember { + val icons = mutableListOf() + for (nested in AllIcons::class.java.nestMembers) { + val fields = nested.declaredFields.filter { it.type.name.contains("Icon") } + icons.addAll(fields.map { (it.get(null) as Icon).toNewIcon() }) + } + icons + } + + var i = 1 + for (x in 0..30) { + Row { + for (y in 0..30) { + Column(modifier = Modifier.size(55.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) { + val icon = icons[i++ % icons.size] + Icon(icon, "Balloon", modifier = Modifier.size(iconSize.dp).padding(10.dp)) + } + } + } + } + } + } + } + } +} + private fun createClockComponent(): JComponent { return compose { val mode = if (System.getProperty("compose.swing.render.on.graphics", "false").toBoolean()) "Swing" else "AWT" diff --git a/plugins/devkit/intellij.devkit.compose/src/showcase/ComposeShowcase.kt b/plugins/devkit/intellij.devkit.compose/src/showcase/ComposeShowcase.kt index 516312fa006d3..bb8af528b98e4 100644 --- a/plugins/devkit/intellij.devkit.compose/src/showcase/ComposeShowcase.kt +++ b/plugins/devkit/intellij.devkit.compose/src/showcase/ComposeShowcase.kt @@ -1,36 +1,16 @@ // 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.devkit.compose.showcase -import androidx.compose.animation.core.EaseInOut -import androidx.compose.animation.core.RepeatMode -import androidx.compose.animation.core.animateDpAsState -import androidx.compose.animation.core.animateFloat -import androidx.compose.animation.core.infiniteRepeatable -import androidx.compose.animation.core.rememberInfiniteTransition -import androidx.compose.animation.core.tween +import androidx.compose.animation.core.* import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.TooltipArea import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.foundation.text.selection.SelectionContainer -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -50,6 +30,7 @@ import com.intellij.icons.AllIcons import com.intellij.openapi.fileChooser.FileChooser import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory import com.intellij.openapi.keymap.KeymapUtil +import com.intellij.openapi.project.Project import com.intellij.ui.UIBundle import com.intellij.util.ui.JBUI import kotlinx.coroutines.Dispatchers.IO @@ -61,20 +42,7 @@ import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.foundation.theme.OverrideDarkMode import org.jetbrains.jewel.ui.Orientation import org.jetbrains.jewel.ui.Outline -import org.jetbrains.jewel.ui.component.CheckboxRow -import org.jetbrains.jewel.ui.component.DefaultButton -import org.jetbrains.jewel.ui.component.Divider -import org.jetbrains.jewel.ui.component.Icon -import org.jetbrains.jewel.ui.component.IconButton -import org.jetbrains.jewel.ui.component.Link -import org.jetbrains.jewel.ui.component.OutlinedButton -import org.jetbrains.jewel.ui.component.RadioButtonRow -import org.jetbrains.jewel.ui.component.TabData -import org.jetbrains.jewel.ui.component.TabStrip -import org.jetbrains.jewel.ui.component.Text -import org.jetbrains.jewel.ui.component.TextField -import org.jetbrains.jewel.ui.component.Tooltip -import org.jetbrains.jewel.ui.component.VerticallyScrollableContainer +import org.jetbrains.jewel.ui.component.* import org.jetbrains.jewel.ui.icons.AllIconsKeys import org.jetbrains.jewel.ui.theme.defaultTabStyle import org.jetbrains.jewel.ui.theme.tooltipStyle @@ -84,8 +52,9 @@ import java.awt.event.KeyEvent import java.io.File import javax.swing.KeyStroke + @Composable -internal fun ComposeShowcase() { +internal fun ComposeShowcase(project: Project) { Column( verticalArrangement = Arrangement.spacedBy(15.dp), modifier = Modifier.padding(10.dp) @@ -110,6 +79,7 @@ internal fun ComposeShowcase() { TextFieldWithButton() TooltipAreaSimple() InfiniteAnimation() + Icons(project) } } } diff --git a/plugins/devkit/intellij.devkit.compose/src/showcase/ComposeShowcaseAction.kt b/plugins/devkit/intellij.devkit.compose/src/showcase/ComposeShowcaseAction.kt index a92106ffecf83..4aece0e6d30aa 100644 --- a/plugins/devkit/intellij.devkit.compose/src/showcase/ComposeShowcaseAction.kt +++ b/plugins/devkit/intellij.devkit.compose/src/showcase/ComposeShowcaseAction.kt @@ -13,9 +13,9 @@ import org.jetbrains.jewel.bridge.compose import java.awt.Dimension import javax.swing.JComponent -private fun createComposeShowcaseComponent(): JComponent { +private fun createComposeShowcaseComponent(project: Project): JComponent { return compose { - ComposeShowcase() + ComposeShowcase(project) } } @@ -31,7 +31,7 @@ internal class ComposeShowcaseAction : DumbAwareAction() { } } -private class ComposeShowcaseDialog(project: Project?, @NlsSafe dialogTitle: String) : +private class ComposeShowcaseDialog(val project: Project?, @NlsSafe dialogTitle: String) : DialogWrapper(project, null, true, IdeModalityType.MODELESS, false) { init { @@ -40,7 +40,7 @@ private class ComposeShowcaseDialog(project: Project?, @NlsSafe dialogTitle: Str } override fun createCenterPanel(): JComponent { - return Wrapper(createComposeShowcaseComponent()).apply { + return Wrapper(createComposeShowcaseComponent(project!!)).apply { minimumSize = Dimension(200, 100) preferredSize = Dimension(800, 600) } diff --git a/plugins/devkit/intellij.devkit.compose/src/showcase/Icons.kt b/plugins/devkit/intellij.devkit.compose/src/showcase/Icons.kt new file mode 100644 index 0000000000000..85d5e7abdbcf5 --- /dev/null +++ b/plugins/devkit/intellij.devkit.compose/src/showcase/Icons.kt @@ -0,0 +1,250 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +@file:OptIn(ExperimentalIconsApi::class) + +package com.intellij.devkit.compose.showcase + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredWidth +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.awt.SwingPanel +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.intellij.icons.AllIcons +import com.intellij.ide.rpc.deserializeFromRpc +import com.intellij.ide.rpc.serializeToRpc +import com.intellij.openapi.project.Project +import com.intellij.openapi.project.guessProjectDir +import com.intellij.openapi.vfs.findFile +import com.intellij.ui.BadgeDotProvider +import com.intellij.ui.BadgeIcon +import com.intellij.ui.SpinningProgressIcon +import com.intellij.util.IconUtil +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.serialization.json.Json +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.Icon +import org.jetbrains.icons.IconManager +import org.jetbrains.icons.design.Circle +import org.jetbrains.icons.design.IconAlign +import org.jetbrains.icons.design.percent +import org.jetbrains.icons.dynamicIcon +import org.jetbrains.icons.icon +import org.jetbrains.icons.legacyIconSupport.swingIcon +import org.jetbrains.icons.legacyIconSupport.toNewIcon +import org.jetbrains.icons.legacyIconSupport.toSwingIcon +import org.jetbrains.icons.modifiers.IconModifier +import org.jetbrains.icons.modifiers.align +import org.jetbrains.icons.modifiers.fillMaxSize +import org.jetbrains.icons.modifiers.patchSvg +import org.jetbrains.icons.modifiers.size +import org.jetbrains.jewel.bridge.toAwtColor +import org.jetbrains.jewel.foundation.ExperimentalJewelApi +import org.jetbrains.jewel.ui.component.Icon +import org.jetbrains.jewel.ui.component.Text +import org.jetbrains.jewel.ui.icon.badge +import org.jetbrains.jewel.ui.icon.tintColor +import javax.swing.BoxLayout +import javax.swing.JLabel +import javax.swing.JPanel + +@OptIn(ExperimentalJewelApi::class) +@Composable +internal fun Icons(project: Project) { + val icons = mutableListOf() + val missingIcon = remember { AllIcons.General.Error.toNewIcon(modifier = IconModifier.fillMaxSize()) } + + val duration = 50L + val animatedIcon = remember { + icon { + animation { + frame(duration) { + swingIcon(AllIcons.Process.Step_1) + } + frame(duration) { + swingIcon(AllIcons.Process.Step_2) + } + frame(duration) { + swingIcon(AllIcons.Process.Step_3) + } + frame(duration) { + swingIcon(AllIcons.Process.Step_4) + } + frame(duration) { + swingIcon(AllIcons.Process.Step_5) + } + frame(duration) { + swingIcon(AllIcons.Process.Step_6) + } + frame(duration) { + swingIcon(AllIcons.Process.Step_7) + } + frame(duration) { + swingIcon(AllIcons.Process.Step_8) + } + } + } + } + + icons.add(remember { + ShowcaseIcon( + icon { + icon(missingIcon) + badge(Color.Green, Circle) + }, + BadgeIcon(AllIcons.General.Error, Color.Green.toAwtColor(), BadgeDotProvider()) + ) + }) + + icons.add(remember { + ShowcaseIcon( + icon { + column(spacing = 5.percent, modifier = IconModifier.fillMaxSize()) { + row(spacing = 5.percent) { + icon(missingIcon, modifier = IconModifier.tintColor(Color.Yellow, BlendMode.Color)) + icon(missingIcon, modifier = IconModifier.tintColor(Color.Green, BlendMode.Hue)) + } + row(spacing = 5.percent) { + icon(missingIcon, modifier = IconModifier.tintColor(Color.Blue, BlendMode.Saturation)) + icon(missingIcon, modifier = IconModifier.tintColor(Color.Cyan, BlendMode.Multiply)) + } + } + icon(animatedIcon, modifier = IconModifier.size(20.percent).align(IconAlign.TopLeft)) + icon(animatedIcon, modifier = IconModifier.size(20.percent).align(IconAlign.TopCenter)) + icon(animatedIcon, modifier = IconModifier.size(20.percent).align(IconAlign.TopRight)) + icon(animatedIcon, modifier = IconModifier.size(20.percent).align(IconAlign.CenterLeft)) + icon(animatedIcon, modifier = IconModifier.size(20.percent).tintColor(Color.Red).align(IconAlign.Center)) + icon(animatedIcon, modifier = IconModifier.size(20.percent).align(IconAlign.CenterRight)) + icon(animatedIcon, modifier = IconModifier.size(20.percent).align(IconAlign.BottomLeft)) + icon(animatedIcon, modifier = IconModifier.size(20.percent).align(IconAlign.BottomCenter)) + icon(animatedIcon, modifier = IconModifier.size(20.percent).align(IconAlign.BottomRight)) + }, + null + ) + }) + + val deferredIcon = remember { + project.guessProjectDir()?.findFile("src/main/kotlin/com/example/demo/test.kt")?.let { + IconUtil.getIcon(it, 0, project) + } + } ?: AllIcons.General.Error + + icons.add( + ShowcaseIcon( + deferredIcon.toNewIcon(), + deferredIcon + ) + ) + + icons.add(remember { + ShowcaseIcon( + animatedIcon, + SpinningProgressIcon() + ) + }) + + val json = Json { serializersModule = IconManager.getInstance().getSerializersModule() } + val dynIcon = dynamicIcon(missingIcon) + val serialized = json.encodeToString(dynIcon) + val deserialized = json.decodeFromString(serialized) + val greenIcon = icon { + icon(missingIcon, modifier = IconModifier.patchSvg { + replaceUnlessMatches("fill", "white", "green") + }) + } + val scope = rememberCoroutineScope() + scope.launch { + delay(5000) + dynIcon.swap(greenIcon) + } + + icons.add(remember { + ShowcaseIcon( + deserialized, + null + ) + }) + + Column(Modifier.fillMaxWidth()) { + // Header + Row(Modifier.fillMaxWidth().padding(vertical = 6.dp)) { + Text("Name", Modifier.weight(1f)) + Text("Role", Modifier.weight(1f)) + } + + // Body + Column(Modifier.fillMaxWidth()) { + Row(Modifier.fillMaxWidth().padding(vertical = 4.dp)) { + Text("a", Modifier.weight(1f)) + Text("b", Modifier.weight(1f)) + } + Row(Modifier.fillMaxWidth().padding(vertical = 4.dp)) { + Text("a", Modifier.weight(1f)) + Text("b", Modifier.weight(1f)) + } + Row(Modifier.fillMaxWidth().padding(vertical = 4.dp)) { + Text("a", Modifier.weight(1f)) + Text("b", Modifier.weight(1f)) + } + } + } + IconsShowcase(icons) +} + +class ShowcaseIcon( + val icon: Icon, + val swingAlternative: javax.swing.Icon?, +) + +@Composable +private fun IconsShowcase(icons: List) { + Text("Compose/Swing new/Swing old") + Row(modifier = Modifier.fillMaxWidth().height(300.dp)) { + Column(modifier = Modifier.requiredWidth(60.dp)) { + for (icon in icons) { + Icon(icon.icon, "Icon") + } + } + wrapStaticSwingIcons( + icons.map { + it.icon.toSwingIcon() + } + ) + wrapStaticSwingIcons( + icons.map { it.swingAlternative ?: AllIcons.General.Warning } + ) + } +} + +@Composable +private fun wrapStaticSwingIcons(icons: List) { + SwingPanel( + factory = { + JPanel().apply { + layout = BoxLayout(this, BoxLayout.Y_AXIS) + for (icon in icons) { + add( + JLabel( + icon + ).apply { + border = null + isOpaque = false + } + ) + } + } + }, + background = Color.Transparent, + modifier = Modifier.width(60.dp).fillMaxHeight(), + ) +} \ No newline at end of file From 2ff6b5c77d70360506f4abe80b2d56144257a665 Mon Sep 17 00:00:00 2001 From: Jakub Senohrabek Date: Tue, 10 Feb 2026 15:29:18 +0100 Subject: [PATCH 2/9] IJPL-176416 New Icons API - fixed bounds, drafted custom image resource loaders GitOrigin-RevId: be0d90ab20c1893c16e1ab3a0f408ce1a962da89 --- platform/icons-api/README.md | 33 +++++- .../icons-api/legacy-icon-support/README.md | 9 +- .../legacy-icon-support/module-content.yaml | 3 + platform/icons-api/module-content.yaml | 3 + platform/icons-api/rendering/BUILD.bazel | 1 + .../intellij.platform.icons.api.rendering.iml | 2 +- .../rendering/lowlevel/module-content.yaml | 3 + .../icons-api/rendering/module-content.yaml | 3 + .../icons/rendering/ImageResource.kt | 1 + .../icons/rendering/ImageResourceProvider.kt | 1 + .../rendering/RescalableImageResource.kt | 14 +-- .../jetbrains/icons}/ImageResourceLoader.kt | 6 +- .../jetbrains/icons/design/IconDesigner.kt | 3 + .../icons-impl/intellij/module-content.yaml | 6 + .../impl/intellij/IntelliJIconManager.kt | 2 +- .../custom/CustomImageResourceLoader.kt | 10 +- .../rendering/IntelliJImageResourceLoader.kt | 2 +- .../IntelliJImageResourceProvider.kt | 2 +- platform/icons-impl/module-content.yaml | 3 + .../icons/impl/design/DefaultIconDesigner.kt | 10 +- .../icons/impl/layers/ImageIconLayer.kt | 2 +- .../icons/impl/layers/LayoutIconLayer.kt | 3 +- .../icons/impl/rendering/SwingPaintingApi.kt | 54 ++++++--- .../layers/ImageIconLayerRenderer.kt | 6 +- .../layers/LayoutIconLayerRenderer.kt | 73 +++++++----- .../int-ui/int-ui-standalone/BUILD.bazel | 2 + ...tellij.platform.jewel.intUi.standalone.iml | 2 +- .../standalone/icon/StandaloneIconDesigner.kt | 2 +- .../icon/StandaloneIconRendererManager.kt | 6 +- platform/jewel/ui/BUILD.bazel | 1 + .../jewel/ui/intellij.platform.jewel.ui.iml | 2 +- .../ui/icon/ComposeImageResourceProvider.kt | 7 +- .../jewel/ui/icon/ResourceImageIconLoader.kt | 2 +- .../src/showcase/Icons.kt | 112 ++++++++---------- 34 files changed, 244 insertions(+), 147 deletions(-) create mode 100644 platform/icons-api/legacy-icon-support/module-content.yaml create mode 100644 platform/icons-api/module-content.yaml create mode 100644 platform/icons-api/rendering/lowlevel/module-content.yaml create mode 100644 platform/icons-api/rendering/module-content.yaml rename platform/icons-api/{rendering/src/org/jetbrains/icons/rendering => src/org/jetbrains/icons}/ImageResourceLoader.kt (59%) create mode 100644 platform/icons-impl/intellij/module-content.yaml create mode 100644 platform/icons-impl/module-content.yaml diff --git a/platform/icons-api/README.md b/platform/icons-api/README.md index 164be818f47ee..7520bb5587c77 100644 --- a/platform/icons-api/README.md +++ b/platform/icons-api/README.md @@ -1,12 +1,12 @@ # Cross frontend-api Icons -These Icons support being rendered by mutlple frontend-apis (swing, compose, can be extended) +These Icons support being rendered by multiple frontend-apis (swing, compose, can be extended) ## Data & Rendering split The API is split to two parts: - [Data](.) - [Rendering](./rendering) -This allows declaring icons without depending on the rendering implementation, which is usefull on backend. +This allows declaring icons without depending on the rendering implementation, which is useful on the backend. ## `org.jetbrains.icons.api.Icon` This is data part, an Icon, which represents how the specific Icon should look like. @@ -42,7 +42,7 @@ val rowLayeredIcon = icon { } } ``` -Row & Column behaves similarly to Compose couterparts. +Row & Column behaves similarly to Compose counterparts. There is also IconModifiers, that can affect how the resulting Icon will look like, again, concept from Compose, you can use modifiers to adjust: - Layouting (margin, size, align @@ -52,21 +52,46 @@ again, concept from Compose, you can use modifiers to adjust: The Icons are going to infer expected size (based on settings, svg data or image data), however, you can also make the icons scaled to the container component. +To serialize an icon, serializers module can be obtained from the IconManager. + ## `org.jetbrains.icons.api.rendering.IconRenderer` While having data object for Icon is great, we still need a way to render it. This class is responsible for getting the previously mentioned Icon data object, it figures out how to load the used images. -Creating this class actually loads resources, so it should be considered a hevy operation. +Creating this class actually loads resources, so it should be considered a heavy operation. This normally shouldn't be used directly, but rather via components, like the Jewel `fun Icon(..)` icon, or specific swing components, that create the renderer themselves. To create a renderer for an Icon, you should use Icon.createRenderer() function. +The final size of the Icon is inferred from the Icon data object but also affected by the scale factor and the containing component. +In compose, the size is affected by modifiers applied to the `Icon()` composable, the swing conunterpart can be configured via the +toSwingIcon() function. (or will be in the future) + ## Legacy/Swing Icon interop To use Swing icons inside the new icons/with the new api, check [legacy-icon-support](./legacy-icon-support/) ## `org.jetbrains.icons.api.IconManager` This interface is responsible for generating Icon models. +## Extensibility +All extensions should be registered beforehand, to allow deserialization, where we need to know +all possible layers, loaders, etc. to properly deserialize icons. That is why it is not enough to +just pass your loaders/layers to the IconManager or whatever when creating the icon. + +### Custom Layer +Custom layers can be easily added by designer's custom function. However, make sure to also register +the layer in the IconManager, to make sure it is serializable, also, ensure to register corresponding Icon Layer Renderer. + +### ImageResourceLoader +Custom image resource loaders can be added. +To do so, you need to implement `org.jetbrains.icons.api.ImageResourceLoader` interface and +then register it via the appropriate IconManager, check individual managers for details. +The registration will tell the IconManager how to serialize this loader and will let +ImageResourceProvider know how to load the resources. +Make sure to implement equals and hashCode functions, as they are used for caching purposes. + +Also, introduce an extension function for IconDesigner to allow easy creation of your new loader. + ## Implementation details For implementation details, refer to [implementation modules](../icons-impl/README.md) \ No newline at end of file diff --git a/platform/icons-api/legacy-icon-support/README.md b/platform/icons-api/legacy-icon-support/README.md index b0c63190eda97..70b5368d1488f 100644 --- a/platform/icons-api/legacy-icon-support/README.md +++ b/platform/icons-api/legacy-icon-support/README.md @@ -12,4 +12,11 @@ There might be some non-implemented integrations related to specific behavior of icon { swingIcon(AllIcons.Actions.AddDirectory) } -``` \ No newline at end of file +``` + +## Serialization +In order to add serialization support for custom swing icons, you have to register it's serializer. +This depends on the current icon manager implementation, for example in IntelliJ, you use extension points. + +Use com.intellij.customLegacyIconSerializer extension inside intellij.platform.icons.impl.intellij module. +Check Jewel docs for registering standalone serializers if supported. \ No newline at end of file diff --git a/platform/icons-api/legacy-icon-support/module-content.yaml b/platform/icons-api/legacy-icon-support/module-content.yaml new file mode 100644 index 0000000000000..fdc907c85c74a --- /dev/null +++ b/platform/icons-api/legacy-icon-support/module-content.yaml @@ -0,0 +1,3 @@ + - name: dist.all/lib/intellij.platform.icons.api.legacyIconSupport.jar + modules: + - name: intellij.platform.icons.api.legacyIconSupport \ No newline at end of file diff --git a/platform/icons-api/module-content.yaml b/platform/icons-api/module-content.yaml new file mode 100644 index 0000000000000..c554822f8bd31 --- /dev/null +++ b/platform/icons-api/module-content.yaml @@ -0,0 +1,3 @@ +- name: dist.all/lib/intellij.platform.icons.api.jar + modules: + - name: intellij.platform.icons.api \ No newline at end of file diff --git a/platform/icons-api/rendering/BUILD.bazel b/platform/icons-api/rendering/BUILD.bazel index 3572b3a4bfb63..5c7e60f3e5908 100644 --- a/platform/icons-api/rendering/BUILD.bazel +++ b/platform/icons-api/rendering/BUILD.bazel @@ -9,6 +9,7 @@ create_kotlinc_options( opt_in = [ "org.jetbrains.icons.api.ExperimentalIconsApi", "org.jetbrains.icons.api.InternalIconsApi", + "org.jetbrains.icons.ExperimentalIconsApi", ], x_x_language = [] ) diff --git a/platform/icons-api/rendering/intellij.platform.icons.api.rendering.iml b/platform/icons-api/rendering/intellij.platform.icons.api.rendering.iml index 903d7703bbe74..95c41f0f8f8bc 100644 --- a/platform/icons-api/rendering/intellij.platform.icons.api.rendering.iml +++ b/platform/icons-api/rendering/intellij.platform.icons.api.rendering.iml @@ -4,7 +4,7 @@ - diff --git a/platform/icons-api/rendering/lowlevel/module-content.yaml b/platform/icons-api/rendering/lowlevel/module-content.yaml new file mode 100644 index 0000000000000..7df87dc04fc82 --- /dev/null +++ b/platform/icons-api/rendering/lowlevel/module-content.yaml @@ -0,0 +1,3 @@ +- name: dist.all/lib/intellij.platform.icons.api.rendering.lowlevel.jar + modules: + - name: intellij.platform.icons.api.rendering.lowlevel \ No newline at end of file diff --git a/platform/icons-api/rendering/module-content.yaml b/platform/icons-api/rendering/module-content.yaml new file mode 100644 index 0000000000000..0340cf0c9059b --- /dev/null +++ b/platform/icons-api/rendering/module-content.yaml @@ -0,0 +1,3 @@ +- name: dist.all/lib/intellij.platform.icons.api.rendering.jar + modules: + - name: intellij.platform.icons.api.rendering \ No newline at end of file diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResource.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResource.kt index 9c311646c6fa3..64a42e85b08c7 100644 --- a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResource.kt +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResource.kt @@ -2,6 +2,7 @@ package org.jetbrains.icons.rendering import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.ImageResourceLoader import org.jetbrains.icons.InternalIconsApi import org.jetbrains.icons.filters.ColorFilter import org.jetbrains.icons.patchers.SvgPatcher diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResourceProvider.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResourceProvider.kt index d62a5bf82a14b..7c551afe8281f 100644 --- a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResourceProvider.kt +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResourceProvider.kt @@ -1,6 +1,7 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.rendering +import org.jetbrains.icons.ImageResourceLoader import org.jetbrains.icons.InternalIconsApi import java.util.ServiceLoader diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/RescalableImageResource.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/RescalableImageResource.kt index 0941de88650b3..ee29efc730b54 100644 --- a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/RescalableImageResource.kt +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/RescalableImageResource.kt @@ -13,12 +13,12 @@ interface RescalableImageResource : ImageResource { @InternalIconsApi sealed interface ImageScale { - fun calculateScalingFactorByOriginalDimensions(width: Int, height: Int? = null): Float + fun calculateScalingFactorByOriginalDimensions(width: Int, height: Int): Float } @InternalIconsApi class FixedScale(val scale: Float) : ImageScale { - override fun calculateScalingFactorByOriginalDimensions(width: Int, height: Int?): Float = scale + override fun calculateScalingFactorByOriginalDimensions(width: Int, height: Int): Float = scale override fun equals(other: Any?): Boolean { if (this === other) return true @@ -36,12 +36,10 @@ class FixedScale(val scale: Float) : ImageScale { @InternalIconsApi class FitAreaScale(val width: Int, val height: Int) : ImageScale { - override fun calculateScalingFactorByOriginalDimensions(width: Int, height: Int?): Float { - val scale = this.width / width.toFloat() - if (height != null) { - return minOf(scale, this.height / height.toFloat()) - } - return scale + override fun calculateScalingFactorByOriginalDimensions(width: Int, height: Int): Float { + val wscale = this.width / width.toFloat() + val hscale = this.height / height.toFloat() + return wscale.coerceAtMost(hscale) } override fun equals(other: Any?): Boolean { diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResourceLoader.kt b/platform/icons-api/src/org/jetbrains/icons/ImageResourceLoader.kt similarity index 59% rename from platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResourceLoader.kt rename to platform/icons-api/src/org/jetbrains/icons/ImageResourceLoader.kt index ced6783c8ce4c..00c68ad6bbd9b 100644 --- a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResourceLoader.kt +++ b/platform/icons-api/src/org/jetbrains/icons/ImageResourceLoader.kt @@ -1,7 +1,5 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.jetbrains.icons.rendering +package org.jetbrains.icons -import org.jetbrains.icons.InternalIconsApi - -@InternalIconsApi +@ExperimentalIconsApi interface ImageResourceLoader \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/design/IconDesigner.kt b/platform/icons-api/src/org/jetbrains/icons/design/IconDesigner.kt index 07eb076c3814c..8353f7ae4ac38 100644 --- a/platform/icons-api/src/org/jetbrains/icons/design/IconDesigner.kt +++ b/platform/icons-api/src/org/jetbrains/icons/design/IconDesigner.kt @@ -3,6 +3,7 @@ package org.jetbrains.icons.design import org.jetbrains.icons.ExperimentalIconsApi import org.jetbrains.icons.Icon +import org.jetbrains.icons.ImageResourceLoader import org.jetbrains.icons.layers.IconLayer import org.jetbrains.icons.modifiers.IconModifier import org.jetbrains.icons.modifiers.align @@ -16,8 +17,10 @@ import org.jetbrains.icons.modifiers.size */ @ExperimentalIconsApi interface IconDesigner { + fun image(resourceLoader: ImageResourceLoader, modifier: IconModifier = IconModifier) fun image(path: String, classLoader: ClassLoader? = null, modifier: IconModifier = IconModifier) fun icon(icon: Icon, modifier: IconModifier = IconModifier) + fun box(modifier: IconModifier = IconModifier, builder: IconDesigner.() -> Unit) fun row(spacing: IconUnit = 0.px, modifier: IconModifier = IconModifier, builder: IconDesigner.() -> Unit) fun column(spacing: IconUnit = 0.px, modifier: IconModifier = IconModifier, builder: IconDesigner.() -> Unit) fun animation(modifier: IconModifier = IconModifier, builder: IconAnimationDesigner.() -> Unit) diff --git a/platform/icons-impl/intellij/module-content.yaml b/platform/icons-impl/intellij/module-content.yaml new file mode 100644 index 0000000000000..0094e28a70744 --- /dev/null +++ b/platform/icons-impl/intellij/module-content.yaml @@ -0,0 +1,6 @@ +- name: dist.all/lib/intellij.platform.icons.impl.intellij.jar + modules: + - name: intellij.platform.icons.impl.intellij + libraries: + jetbrains.kotlinx.serialization.core.jvm: + - name: $MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-core-jvm/1/kotlinx-serialization-core-jvm-1.jar \ No newline at end of file diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt index f69758ab4571f..0ca4fb0797ee9 100644 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt @@ -17,7 +17,7 @@ import org.jetbrains.icons.impl.intellij.rendering.IntelliJImageResourceLoaderSe import org.jetbrains.icons.impl.intellij.rendering.IntelliJImageResourceProvider import org.jetbrains.icons.impl.rendering.SwingIcon import org.jetbrains.icons.rendering.IconRendererManager -import org.jetbrains.icons.rendering.ImageResourceLoader +import org.jetbrains.icons.ImageResourceLoader import org.jetbrains.icons.rendering.ImageResourceProvider class IntelliJIconManager : DefaultIconManager(), SwingIconManager { diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomImageResourceLoader.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomImageResourceLoader.kt index 86c6e195921a8..798a4bc80ca01 100644 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomImageResourceLoader.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomImageResourceLoader.kt @@ -1,14 +1,18 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.impl.intellij.custom +import com.intellij.ui.scale.ScaleContext import org.jetbrains.annotations.ApiStatus -import org.jetbrains.icons.rendering.ImageResourceLoader -import javax.swing.Icon +import org.jetbrains.icons.ImageResourceLoader +import org.jetbrains.icons.rendering.ImageModifiers +import java.awt.Image import kotlin.reflect.KClass -abstract class CustomImageResourceLoader( +abstract class CustomImageResourceLoader( klass: KClass ): CustomSerializableRegistration(klass) { + abstract fun loadImage(loader: T, scale: ScaleContext, imageModifiers: ImageModifiers?): Image? + @ApiStatus.Internal companion object: CustomSerializableRegistration.Companion>( ImageResourceLoader::class, diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJImageResourceLoader.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJImageResourceLoader.kt index 9901f76d58a9c..f62bd4bde73bd 100644 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJImageResourceLoader.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJImageResourceLoader.kt @@ -14,7 +14,7 @@ import org.jetbrains.icons.impl.rendering.DefaultImageModifiers import org.jetbrains.icons.modifiers.svgPatcher import org.jetbrains.icons.patchers.combineWith import org.jetbrains.icons.rendering.ImageModifiers -import org.jetbrains.icons.rendering.ImageResourceLoader +import org.jetbrains.icons.ImageResourceLoader import java.awt.Image import java.awt.image.ImageFilter diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJImageResourceProvider.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJImageResourceProvider.kt index 8d9acf24b6fc3..ad39da4d8fa70 100644 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJImageResourceProvider.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJImageResourceProvider.kt @@ -13,7 +13,7 @@ import org.jetbrains.icons.rendering.Bounds import org.jetbrains.icons.rendering.EmptyBitmapImageResource import org.jetbrains.icons.rendering.ImageModifiers import org.jetbrains.icons.rendering.ImageResource -import org.jetbrains.icons.rendering.ImageResourceLoader +import org.jetbrains.icons.ImageResourceLoader import org.jetbrains.icons.rendering.ImageScale import org.jetbrains.icons.rendering.RescalableImageResource import org.jetbrains.icons.impl.rendering.AwtImageResource diff --git a/platform/icons-impl/module-content.yaml b/platform/icons-impl/module-content.yaml new file mode 100644 index 0000000000000..25c0071bbabc8 --- /dev/null +++ b/platform/icons-impl/module-content.yaml @@ -0,0 +1,3 @@ +- name: dist.all/lib/intellij.platform.icons.impl.jar + modules: + - name: intellij.platform.icons.impl \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/design/DefaultIconDesigner.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/design/DefaultIconDesigner.kt index e4dff9baedec2..4d55b98a06b9d 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/design/DefaultIconDesigner.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/design/DefaultIconDesigner.kt @@ -14,14 +14,14 @@ import org.jetbrains.icons.impl.layers.IconIconLayer import org.jetbrains.icons.layers.IconLayer import org.jetbrains.icons.impl.layers.ImageIconLayer import org.jetbrains.icons.impl.layers.LayoutIconLayer -import org.jetbrains.icons.rendering.ImageResourceLoader +import org.jetbrains.icons.ImageResourceLoader import org.jetbrains.icons.impl.layers.ShapeIconLayer abstract class DefaultIconDesigner: IconDesigner { private val layers = mutableListOf() - protected fun image(loader: ImageResourceLoader, modifier: IconModifier) { - layers.add(ImageIconLayer(loader, modifier)) + override fun image(resourceLoader: ImageResourceLoader, modifier: IconModifier) { + layers.add(ImageIconLayer(resourceLoader, modifier)) } override fun icon(icon: Icon, modifier: IconModifier) { @@ -32,6 +32,10 @@ abstract class DefaultIconDesigner: IconDesigner { layers.add(iconLayer) } + override fun box(modifier: IconModifier, builder: IconDesigner.() -> Unit) { + layout(LayoutIconLayer.LayoutDirection.Box, IconUnit.Zero, modifier, builder) + } + override fun row(spacing: IconUnit, modifier: IconModifier, builder: IconDesigner.() -> Unit) { layout(LayoutIconLayer.LayoutDirection.Row, spacing, modifier, builder) } diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/layers/ImageIconLayer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/ImageIconLayer.kt index 79a9cf5ba6d29..d885206cad0b6 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/layers/ImageIconLayer.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/ImageIconLayer.kt @@ -4,7 +4,7 @@ package org.jetbrains.icons.impl.layers import kotlinx.serialization.Serializable import org.jetbrains.icons.layers.IconLayer import org.jetbrains.icons.modifiers.IconModifier -import org.jetbrains.icons.rendering.ImageResourceLoader +import org.jetbrains.icons.ImageResourceLoader @Serializable class ImageIconLayer( diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/layers/LayoutIconLayer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/LayoutIconLayer.kt index e2be1efa58d7c..9a6b3da829c72 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/layers/LayoutIconLayer.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/LayoutIconLayer.kt @@ -39,6 +39,7 @@ class LayoutIconLayer( enum class LayoutDirection { Row, - Column + Column, + Box } } \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingPaintingApi.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingPaintingApi.kt index eaf58001af6da..6221af03e92ca 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingPaintingApi.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingPaintingApi.kt @@ -25,25 +25,39 @@ class SwingPaintingApi( val g: Graphics, val x: Int, val y: Int, + private val customX: Int? = null, + private val customY: Int? = null, private val customWidth: Int? = null, private val customHeight: Int? = null, private val overrideColorFilter: ColorFilter? = null, - override val scaling: ScalingContext = SwingScalingContext(1f) + override val scaling: ScalingContext = SwingScalingContext(1f), ) : PaintingApi { - override val bounds: Bounds get() { - if (c == null) return Bounds(0, 0, customWidth ?: 0, customHeight ?: 0) - return Bounds( - (x * scaling.display).toInt(), - (y * scaling.display).toInt(), - customWidth ?: (c.width * scaling.display).toInt(), - customHeight ?: (c.height * scaling.display).toInt() - ) - } + override val bounds: Bounds + get() { + if (c == null) return Bounds(0, 0, customWidth ?: 0, customHeight ?: 0) + return Bounds( + customX ?: (x * scaling.display).toInt(), + customY ?: (y * scaling.display).toInt(), + customWidth ?: (c.width * scaling.display).toInt(), + customHeight ?: (c.height * scaling.display).toInt() + ) + } override fun getUsedBounds(): Bounds = bounds override fun withCustomContext(bounds: Bounds, overrideColorFilter: ColorFilter?): PaintingApi { - return SwingPaintingApi(c, g, bounds.x, bounds.y, bounds.width, bounds.height, overrideColorFilter ?: this.overrideColorFilter) + return SwingPaintingApi( + c, + g, + x, + y, + bounds.x, + bounds.y, + bounds.width, + bounds.height, + overrideColorFilter ?: this.overrideColorFilter, + scaling + ) } override fun drawCircle(color: Color, x: Int, y: Int, radius: Float, alpha: Float, mode: DrawMode) { @@ -67,10 +81,12 @@ class SwingPaintingApi( g.color = color.toAwtColor() if (mode == DrawMode.Stroke) { g.draw(shape) - } else { + } + else { g.fill(shape) } - } finally { + } + finally { g.composite = oldComposite g.paint = oldPaint } @@ -88,7 +104,7 @@ class SwingPaintingApi( srcWidth: Int?, srcHeight: Int?, alpha: Float, - colorFilter: ColorFilter? + colorFilter: ColorFilter?, ) { when (image) { is BitmapImageResource -> { @@ -117,7 +133,7 @@ class SwingPaintingApi( srcWidth: Int?, srcHeight: Int?, alpha: Float, - colorFilter: ColorFilter? + colorFilter: ColorFilter?, ) { val swingImage = image.scale(FitAreaScale(width ?: bounds.width, height ?: bounds.height)) drawImage(swingImage, x, y, width, height, srcX, srcY, srcWidth, srcHeight, alpha, colorFilter) @@ -134,7 +150,7 @@ class SwingPaintingApi( srcWidth: Int?, srcHeight: Int?, alpha: Float, - colorFilter: ColorFilter? + colorFilter: ColorFilter?, ) { val swingImage = image.awtImage() @@ -164,7 +180,7 @@ class SwingPaintingApi( srcWidth: Int?, srcHeight: Int?, alpha: Float, - colorFilter: ColorFilter? + colorFilter: ColorFilter?, ) { // TODO apply alpha & color filters @@ -191,8 +207,8 @@ class SwingPaintingApi( fun PaintingApi.swing(): SwingPaintingApi? = this as? SwingPaintingApi internal class SwingScalingContext( - override val display: Float -): ScalingContext { + override val display: Float, +) : ScalingContext { override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/ImageIconLayerRenderer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/ImageIconLayerRenderer.kt index 236b207a6288d..c2ae36cee330c 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/ImageIconLayerRenderer.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/ImageIconLayerRenderer.kt @@ -29,12 +29,14 @@ abstract class BaseImageIconLayerRenderer: IconLayerRenderer { override fun render(api: PaintingApi) { val currentImage = image + val w = api.scaling.applyTo(currentImage.width) + val h = api.scaling.applyTo(currentImage.height) val layout = DefaultLayerLayout( Bounds( 0, 0, - api.scaling.applyTo(currentImage.width) ?: api.bounds.width, - api.scaling.applyTo(currentImage.height) ?: api.bounds.width, + api.bounds.width.coerceAtMost(w ?: Integer.MAX_VALUE), + api.bounds.height.coerceAtMost(h ?: Integer.MAX_VALUE) ), api.bounds ) diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/LayoutIconLayerRenderer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/LayoutIconLayerRenderer.kt index e90776b3caa42..f3f84e7675bdc 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/LayoutIconLayerRenderer.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/LayoutIconLayerRenderer.kt @@ -13,7 +13,7 @@ import org.jetbrains.icons.impl.rendering.modifiers.asPixels class LayoutIconLayerRenderer( private val layoutLayer: LayoutIconLayer, - private val renderingContext: RenderingContext, + renderingContext: RenderingContext, ) : IconLayerRenderer { private val currentContext = renderingContext.adjustTo(layoutLayer) private val nestedRenderers = createNestedRenderers(layoutLayer) @@ -35,25 +35,33 @@ class LayoutIconLayerRenderer( val appliedLayout = layoutLayer.modifier.applyTo(layout, api.scaling) val finalBounds = appliedLayout.calculateFinalBounds() - if (layoutLayer.direction == LayoutIconLayer.LayoutDirection.Row) { - val spacingPx = layoutLayer.spacing.asPixels(api.scaling, api.bounds, true) - val remainingSize = finalBounds.width - spacingPx * (nestedRenderers.count() - 1) - val size = remainingSize / nestedRenderers.count() - var offset = 0 - for (nestedRenderer in nestedRenderers) { - val nestedApi = api.withCustomContext(Bounds(finalBounds.x + offset, finalBounds.y, size, finalBounds.height)) - nestedRenderer.render(nestedApi) - offset += size + spacingPx + when (layoutLayer.direction) { + LayoutIconLayer.LayoutDirection.Row -> { + val spacingPx = layoutLayer.spacing.asPixels(api.scaling, api.bounds, true) + val remainingSize = finalBounds.width - spacingPx * (nestedRenderers.count() - 1) + val size = remainingSize / nestedRenderers.count() + var offset = 0 + for (nestedRenderer in nestedRenderers) { + val nestedApi = api.withCustomContext(Bounds(finalBounds.x + offset, finalBounds.y, size, finalBounds.height)) + nestedRenderer.render(nestedApi) + offset += size + spacingPx + } } - } else { - val spacingPx = layoutLayer.spacing.asPixels(api.scaling, api.bounds, false) - val remainingSize = finalBounds.height - spacingPx * (nestedRenderers.count() - 1) - val size = remainingSize / nestedRenderers.count() - var offset = 0 - for (nestedRenderer in nestedRenderers) { - val nestedApi = api.withCustomContext(Bounds(finalBounds.x, finalBounds.y + offset, finalBounds.width, size)) - nestedRenderer.render(nestedApi) - offset += size + spacingPx + LayoutIconLayer.LayoutDirection.Column -> { + val spacingPx = layoutLayer.spacing.asPixels(api.scaling, api.bounds, false) + val remainingSize = finalBounds.height - spacingPx * (nestedRenderers.count() - 1) + val size = remainingSize / nestedRenderers.count() + var offset = 0 + for (nestedRenderer in nestedRenderers) { + val nestedApi = api.withCustomContext(Bounds(finalBounds.x, finalBounds.y + offset, finalBounds.width, size)) + nestedRenderer.render(nestedApi) + offset += size + spacingPx + } + } + else -> { + for (nestedRenderer in nestedRenderers) { + nestedRenderer.render(api) + } } } } @@ -63,15 +71,26 @@ class LayoutIconLayerRenderer( var height = 0 for (layer in nestedRenderers) { val dimensions = layer.calculateExpectedDimensions(scaling) - if (layoutLayer.direction == LayoutIconLayer.LayoutDirection.Row) { - width += dimensions.width - if (dimensions.height > height) { - height = dimensions.height + when (layoutLayer.direction) { + LayoutIconLayer.LayoutDirection.Row -> { + width += dimensions.width + if (dimensions.height > height) { + height = dimensions.height + } + } + LayoutIconLayer.LayoutDirection.Column -> { + height += dimensions.height + if (dimensions.width > width) { + width = dimensions.width + } } - } else { - height += dimensions.height - if (dimensions.width > width) { - width = dimensions.width + else -> { + if (dimensions.width > width) { + width = dimensions.width + } + if (dimensions.height > height) { + height = dimensions.height + } } } } diff --git a/platform/jewel/int-ui/int-ui-standalone/BUILD.bazel b/platform/jewel/int-ui/int-ui-standalone/BUILD.bazel index cb3775032f450..6dfd3d9ef89e6 100644 --- a/platform/jewel/int-ui/int-ui-standalone/BUILD.bazel +++ b/platform/jewel/int-ui/int-ui-standalone/BUILD.bazel @@ -10,6 +10,8 @@ create_kotlinc_options( "org.jetbrains.jewel.foundation.ExperimentalJewelApi", "org.jetbrains.jewel.foundation.InternalJewelApi", "org.jetbrains.icons.api.InternalIconsApi", + "org.jetbrains.icons.InternalIconsApi", + "org.jetbrains.icons.ExperimentalIconsApi", ], x_context_parameters = True, x_explicit_api_mode = "strict" diff --git a/platform/jewel/int-ui/int-ui-standalone/intellij.platform.jewel.intUi.standalone.iml b/platform/jewel/int-ui/int-ui-standalone/intellij.platform.jewel.intUi.standalone.iml index bd01e74366eac..47c80e1a60419 100644 --- a/platform/jewel/int-ui/int-ui-standalone/intellij.platform.jewel.intUi.standalone.iml +++ b/platform/jewel/int-ui/int-ui-standalone/intellij.platform.jewel.intUi.standalone.iml @@ -4,7 +4,7 @@ - diff --git a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconDesigner.kt b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconDesigner.kt index b01b0175f4082..1b5a97bc4e24e 100644 --- a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconDesigner.kt +++ b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconDesigner.kt @@ -1,8 +1,8 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.jewel.intui.standalone.icon -import org.jetbrains.icons.modifiers.IconModifier import org.jetbrains.icons.impl.design.DefaultIconDesigner +import org.jetbrains.icons.modifiers.IconModifier import org.jetbrains.jewel.ui.icon.PathImageResourceLoader internal class StandaloneIconDesigner : DefaultIconDesigner() { diff --git a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconRendererManager.kt b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconRendererManager.kt index 4a801f732019f..f0c2649d93e4d 100644 --- a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconRendererManager.kt +++ b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconRendererManager.kt @@ -6,15 +6,13 @@ package org.jetbrains.jewel.intui.standalone.icon import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.FlowCollector -import kotlinx.serialization.KSerializer import org.jetbrains.icons.ExperimentalIconsApi import org.jetbrains.icons.Icon +import org.jetbrains.icons.impl.rendering.CoroutineBasedMutableIconUpdateFlow +import org.jetbrains.icons.impl.rendering.DefaultIconRendererManager import org.jetbrains.icons.rendering.ImageModifiers import org.jetbrains.icons.rendering.MutableIconUpdateFlow import org.jetbrains.icons.rendering.RenderingContext -import org.jetbrains.icons.layers.IconLayer -import org.jetbrains.icons.impl.rendering.CoroutineBasedMutableIconUpdateFlow -import org.jetbrains.icons.impl.rendering.DefaultIconRendererManager @OptIn(ExperimentalIconsApi::class) internal class StandaloneIconRendererManager : DefaultIconRendererManager() { diff --git a/platform/jewel/ui/BUILD.bazel b/platform/jewel/ui/BUILD.bazel index a2055e77ab096..677048bf52540 100644 --- a/platform/jewel/ui/BUILD.bazel +++ b/platform/jewel/ui/BUILD.bazel @@ -11,6 +11,7 @@ create_kotlinc_options( "org.jetbrains.jewel.foundation.InternalJewelApi", "org.jetbrains.icons.api.InternalIconsApi", "org.jetbrains.icons.api.ExperimentalIconsApi", + "org.jetbrains.icons.ExperimentalIconsApi", ], x_context_parameters = True, x_explicit_api_mode = "strict" diff --git a/platform/jewel/ui/intellij.platform.jewel.ui.iml b/platform/jewel/ui/intellij.platform.jewel.ui.iml index 775be879190ff..2feb289443520 100644 --- a/platform/jewel/ui/intellij.platform.jewel.ui.iml +++ b/platform/jewel/ui/intellij.platform.jewel.ui.iml @@ -4,7 +4,7 @@ - diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeImageResourceProvider.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeImageResourceProvider.kt index 31cd9c600c37d..2bbd9a5acbdb3 100644 --- a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeImageResourceProvider.kt +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeImageResourceProvider.kt @@ -9,6 +9,8 @@ import javax.xml.XMLConstants import javax.xml.parsers.DocumentBuilderFactory import org.jetbrains.annotations.ApiStatus import org.jetbrains.compose.resources.decodeToSvgPainter +import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.icons.InternalIconsApi import org.jetbrains.icons.design.Color import org.jetbrains.icons.modifiers.svgPatcher import org.jetbrains.icons.patchers.SvgPatchOperation @@ -16,15 +18,17 @@ import org.jetbrains.icons.patchers.SvgPatcher import org.jetbrains.icons.patchers.combineWith import org.jetbrains.icons.rendering.ImageModifiers import org.jetbrains.icons.rendering.ImageResource -import org.jetbrains.icons.rendering.ImageResourceLoader +import org.jetbrains.icons.ImageResourceLoader import org.jetbrains.icons.rendering.ImageResourceProvider import org.jetbrains.icons.impl.rendering.DefaultImageModifiers import org.jetbrains.jewel.foundation.InternalJewelApi import org.jetbrains.jewel.ui.painter.writeToString import org.w3c.dom.Element +@OptIn(ExperimentalIconsApi::class) @InternalJewelApi @ApiStatus.Internal +@InternalIconsApi public class ComposeImageResourceProvider : ImageResourceProvider { override fun loadImage(loader: ImageResourceLoader, imageModifiers: ImageModifiers?): ImageResource { // TODO Support image modifiers @@ -46,6 +50,7 @@ public class ComposeImageResourceProvider : ImageResourceProvider { private val documentBuilderFactory = DocumentBuilderFactory.newDefaultInstance().apply { setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true) } +@InternalIconsApi private fun patchSvg(modifiers: ImageModifiers?, inputStream: InputStream): ByteArray { val builder = documentBuilderFactory.newDocumentBuilder() val document = builder.parse(inputStream) diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ResourceImageIconLoader.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ResourceImageIconLoader.kt index 52c5a56c04796..ec94ee2a1cf67 100644 --- a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ResourceImageIconLoader.kt +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ResourceImageIconLoader.kt @@ -2,7 +2,7 @@ package org.jetbrains.jewel.ui.icon import org.jetbrains.annotations.ApiStatus -import org.jetbrains.icons.rendering.ImageResourceLoader +import org.jetbrains.icons.ImageResourceLoader import org.jetbrains.jewel.foundation.InternalJewelApi @InternalJewelApi diff --git a/plugins/devkit/intellij.devkit.compose/src/showcase/Icons.kt b/plugins/devkit/intellij.devkit.compose/src/showcase/Icons.kt index 85d5e7abdbcf5..f0741553e8cfa 100644 --- a/plugins/devkit/intellij.devkit.compose/src/showcase/Icons.kt +++ b/plugins/devkit/intellij.devkit.compose/src/showcase/Icons.kt @@ -102,10 +102,12 @@ internal fun Icons(project: Project) { icon(missingIcon) badge(Color.Green, Circle) }, - BadgeIcon(AllIcons.General.Error, Color.Green.toAwtColor(), BadgeDotProvider()) + BadgeIcon(AllIcons.General.Error, Color.Green.toAwtColor(), BadgeDotProvider()), + "Icon with Badge" ) }) + val size = 30.percent icons.add(remember { ShowcaseIcon( icon { @@ -119,22 +121,23 @@ internal fun Icons(project: Project) { icon(missingIcon, modifier = IconModifier.tintColor(Color.Cyan, BlendMode.Multiply)) } } - icon(animatedIcon, modifier = IconModifier.size(20.percent).align(IconAlign.TopLeft)) - icon(animatedIcon, modifier = IconModifier.size(20.percent).align(IconAlign.TopCenter)) - icon(animatedIcon, modifier = IconModifier.size(20.percent).align(IconAlign.TopRight)) - icon(animatedIcon, modifier = IconModifier.size(20.percent).align(IconAlign.CenterLeft)) - icon(animatedIcon, modifier = IconModifier.size(20.percent).tintColor(Color.Red).align(IconAlign.Center)) - icon(animatedIcon, modifier = IconModifier.size(20.percent).align(IconAlign.CenterRight)) - icon(animatedIcon, modifier = IconModifier.size(20.percent).align(IconAlign.BottomLeft)) - icon(animatedIcon, modifier = IconModifier.size(20.percent).align(IconAlign.BottomCenter)) - icon(animatedIcon, modifier = IconModifier.size(20.percent).align(IconAlign.BottomRight)) + icon(animatedIcon, modifier = IconModifier.size(size).align(IconAlign.TopLeft)) + icon(animatedIcon, modifier = IconModifier.size(size).align(IconAlign.TopCenter)) + icon(animatedIcon, modifier = IconModifier.size(size).align(IconAlign.TopRight)) + icon(animatedIcon, modifier = IconModifier.size(size).align(IconAlign.CenterLeft)) + icon(animatedIcon, modifier = IconModifier.size(size).tintColor(Color.Red).align(IconAlign.Center)) + icon(animatedIcon, modifier = IconModifier.size(size).align(IconAlign.CenterRight)) + icon(animatedIcon, modifier = IconModifier.size(size).align(IconAlign.BottomLeft)) + icon(animatedIcon, modifier = IconModifier.size(size).align(IconAlign.BottomCenter)) + icon(animatedIcon, modifier = IconModifier.size(size).align(IconAlign.BottomRight)) }, - null + null, + "Complex Layout Icon" ) }) val deferredIcon = remember { - project.guessProjectDir()?.findFile("src/main/kotlin/com/example/demo/test.kt")?.let { + project.guessProjectDir()?.findFile("build.gradle.kts")?.let { IconUtil.getIcon(it, 0, project) } } ?: AllIcons.General.Error @@ -142,14 +145,16 @@ internal fun Icons(project: Project) { icons.add( ShowcaseIcon( deferredIcon.toNewIcon(), - deferredIcon + deferredIcon, + "Deferred Icon" ) ) icons.add(remember { ShowcaseIcon( animatedIcon, - SpinningProgressIcon() + SpinningProgressIcon(), + "Animated Icon" ) }) @@ -171,80 +176,65 @@ internal fun Icons(project: Project) { icons.add(remember { ShowcaseIcon( deserialized, - null + null, + "Deserialized Dynamic Icon" ) }) Column(Modifier.fillMaxWidth()) { // Header Row(Modifier.fillMaxWidth().padding(vertical = 6.dp)) { - Text("Name", Modifier.weight(1f)) - Text("Role", Modifier.weight(1f)) + Text("Title", Modifier.weight(1f)) + Text("Compose", Modifier.weight(1f)) + Text("Swing", Modifier.weight(1f)) + Text("Old Api - Swing", Modifier.weight(1f)) } // Body Column(Modifier.fillMaxWidth()) { - Row(Modifier.fillMaxWidth().padding(vertical = 4.dp)) { - Text("a", Modifier.weight(1f)) - Text("b", Modifier.weight(1f)) - } - Row(Modifier.fillMaxWidth().padding(vertical = 4.dp)) { - Text("a", Modifier.weight(1f)) - Text("b", Modifier.weight(1f)) - } - Row(Modifier.fillMaxWidth().padding(vertical = 4.dp)) { - Text("a", Modifier.weight(1f)) - Text("b", Modifier.weight(1f)) + for (icon in icons) { + Row(Modifier.fillMaxWidth().padding(vertical = 4.dp)) { + Text(icon.title, Modifier.weight(1f)) + Column(Modifier.weight(1f)) { + Icon(icon.icon, "Icon") + } + wrapStaticSwingIcon( + icon.icon.toSwingIcon(), + modifier = Modifier.weight(1f) + ) + wrapStaticSwingIcon( + icon.swingAlternative ?: AllIcons.General.Warning, + modifier = Modifier.weight(1f) + ) + } } } } - IconsShowcase(icons) } class ShowcaseIcon( val icon: Icon, val swingAlternative: javax.swing.Icon?, + val title: String ) @Composable -private fun IconsShowcase(icons: List) { - Text("Compose/Swing new/Swing old") - Row(modifier = Modifier.fillMaxWidth().height(300.dp)) { - Column(modifier = Modifier.requiredWidth(60.dp)) { - for (icon in icons) { - Icon(icon.icon, "Icon") - } - } - wrapStaticSwingIcons( - icons.map { - it.icon.toSwingIcon() - } - ) - wrapStaticSwingIcons( - icons.map { it.swingAlternative ?: AllIcons.General.Warning } - ) - } -} - -@Composable -private fun wrapStaticSwingIcons(icons: List) { +private fun wrapStaticSwingIcon(icon: javax.swing.Icon, modifier: Modifier = Modifier) { SwingPanel( factory = { JPanel().apply { layout = BoxLayout(this, BoxLayout.Y_AXIS) - for (icon in icons) { - add( - JLabel( - icon - ).apply { - border = null - isOpaque = false - } - ) - } + add( + JLabel( + icon + ).apply { + border = null + isOpaque = false + } + ) } }, background = Color.Transparent, - modifier = Modifier.width(60.dp).fillMaxHeight(), + modifier = modifier.fillMaxHeight(), ) } \ No newline at end of file From 788c70ae90300cc0150174d571355cc195fea0c4 Mon Sep 17 00:00:00 2001 From: Jakub Senohrabek Date: Wed, 11 Feb 2026 14:37:59 +0100 Subject: [PATCH 3/9] IJPL-176416 New Icons API - fixed standalone setup GitOrigin-RevId: e1993b24c61995c2b933554dab44cec34a484733 --- platform/icons-api/build.gradle.kts | 1 + platform/icons-api/rendering/build.gradle.kts | 1 + platform/icons-impl/build.gradle.kts | 1 + .../impl/intellij/IntelliJIconManager.kt | 5 +- .../icons/impl/intellij/DynamicIconTest.kt | 7 ++- ...anager => org.jetbrains.icons.IconManager} | 0 ...rains.icons.rendering.IconRendererManager} | 0 ...ins.icons.rendering.ImageResourceProvider} | 0 platform/jewel/samples/showcase/BUILD.bazel | 4 +- ...tellij.platform.jewel.samples.showcase.iml | 2 +- .../ui/icon/ComposeImageResourceProvider.kt | 50 ++++++++++++++++--- .../jewel/ui/icon/ResourceImageIconLoader.kt | 26 ++++++++-- 12 files changed, 79 insertions(+), 18 deletions(-) rename platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/{org.jetbrains.icons.api.IconManager => org.jetbrains.icons.IconManager} (100%) rename platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/{org.jetbrains.icons.api.rendering.IconRendererManager => org.jetbrains.icons.rendering.IconRendererManager} (100%) rename platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/{org.jetbrains.icons.api.rendering.ImageResourceProvider => org.jetbrains.icons.rendering.ImageResourceProvider} (100%) diff --git a/platform/icons-api/build.gradle.kts b/platform/icons-api/build.gradle.kts index 622e6a526c586..67042e4f39cdf 100644 --- a/platform/icons-api/build.gradle.kts +++ b/platform/icons-api/build.gradle.kts @@ -2,6 +2,7 @@ plugins { jewel + alias(libs.plugins.kotlinx.serialization) } sourceSets { diff --git a/platform/icons-api/rendering/build.gradle.kts b/platform/icons-api/rendering/build.gradle.kts index 67e9c65229afc..f94cde35d82ea 100644 --- a/platform/icons-api/rendering/build.gradle.kts +++ b/platform/icons-api/rendering/build.gradle.kts @@ -2,6 +2,7 @@ plugins { jewel + alias(libs.plugins.kotlinx.serialization) } sourceSets { diff --git a/platform/icons-impl/build.gradle.kts b/platform/icons-impl/build.gradle.kts index 69e56dc7d7560..ac9a9410896a8 100644 --- a/platform/icons-impl/build.gradle.kts +++ b/platform/icons-impl/build.gradle.kts @@ -2,6 +2,7 @@ plugins { jewel + alias(libs.plugins.kotlinx.serialization) } sourceSets { diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt index 0ca4fb0797ee9..05bce75162d5e 100644 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt @@ -28,8 +28,9 @@ class IntelliJIconManager : DefaultIconManager(), SwingIconManager { } override fun dynamicIcon(icon: Icon): DynamicIcon { - return createDynamicIcon(icon) { dynamicIcon, content -> - // TODO Send updates over network (also listen for them) + return createDynamicIcon(icon) { _, _ -> + // TODO Send updates over network and across serialization (also listen for them) + // When implemeted, also uncomment DynamicIconTest } } diff --git a/platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DynamicIconTest.kt b/platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DynamicIconTest.kt index 0b435c86b4e6b..237e2660f5540 100644 --- a/platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DynamicIconTest.kt +++ b/platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DynamicIconTest.kt @@ -1,4 +1,7 @@ -// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Not possible before DynamicIcon can be synced cross-serialization + + +/*// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.impl.intellij import com.intellij.testFramework.junit5.TestApplication @@ -55,4 +58,4 @@ class DynamicIconTest { assertThat(lastChangeId).isGreaterThan(0) } } -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.api.IconManager b/platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.IconManager similarity index 100% rename from platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.api.IconManager rename to platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.IconManager diff --git a/platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.api.rendering.IconRendererManager b/platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.rendering.IconRendererManager similarity index 100% rename from platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.api.rendering.IconRendererManager rename to platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.rendering.IconRendererManager diff --git a/platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.api.rendering.ImageResourceProvider b/platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.rendering.ImageResourceProvider similarity index 100% rename from platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.api.rendering.ImageResourceProvider rename to platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.rendering.ImageResourceProvider diff --git a/platform/jewel/samples/showcase/BUILD.bazel b/platform/jewel/samples/showcase/BUILD.bazel index 952dd32e26a85..f771b4442c6f5 100644 --- a/platform/jewel/samples/showcase/BUILD.bazel +++ b/platform/jewel/samples/showcase/BUILD.bazel @@ -9,7 +9,7 @@ create_kotlinc_options( "androidx.compose.foundation.ExperimentalFoundationApi", "org.jetbrains.jewel.foundation.ExperimentalJewelApi", "org.jetbrains.jewel.foundation.InternalJewelApi", - "org.jetbrains.icons.api.ExperimentalIconsApi", + "org.jetbrains.icons.ExperimentalIconsApi", ], plugin_options = ["plugin:androidx.compose.compiler.plugins.kotlin:generateFunctionKeyMetaAnnotations=true"], x_context_parameters = True, @@ -61,4 +61,4 @@ jvm_library( ], plugins = ["@lib//:compose-plugin"] ) -### auto-generated section `build intellij.platform.jewel.samples.showcase` end \ No newline at end of file +### auto-generated section `build intellij.platform.jewel.samples.showcase` end diff --git a/platform/jewel/samples/showcase/intellij.platform.jewel.samples.showcase.iml b/platform/jewel/samples/showcase/intellij.platform.jewel.samples.showcase.iml index e747da0e3eb9c..bb519850b1005 100644 --- a/platform/jewel/samples/showcase/intellij.platform.jewel.samples.showcase.iml +++ b/platform/jewel/samples/showcase/intellij.platform.jewel.samples.showcase.iml @@ -4,7 +4,7 @@ - diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeImageResourceProvider.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeImageResourceProvider.kt index 2bbd9a5acbdb3..c6d955063ab57 100644 --- a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeImageResourceProvider.kt +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeImageResourceProvider.kt @@ -1,6 +1,7 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.jewel.ui.icon +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.decodeToImageBitmap import androidx.compose.ui.unit.Density import java.io.ByteArrayInputStream @@ -11,7 +12,6 @@ import org.jetbrains.annotations.ApiStatus import org.jetbrains.compose.resources.decodeToSvgPainter import org.jetbrains.icons.ExperimentalIconsApi import org.jetbrains.icons.InternalIconsApi -import org.jetbrains.icons.design.Color import org.jetbrains.icons.modifiers.svgPatcher import org.jetbrains.icons.patchers.SvgPatchOperation import org.jetbrains.icons.patchers.SvgPatcher @@ -34,12 +34,12 @@ public class ComposeImageResourceProvider : ImageResourceProvider { // TODO Support image modifiers if (loader is PathImageResourceLoader) { val extension = loader.path.substringAfterLast(".").lowercase() - val data = loader.loadData() + val data = loader.loadData(imageModifiers) val stream = ByteArrayInputStream(data) return when (extension) { "svg" -> ComposePainterImageResource(patchSvg(imageModifiers, stream).decodeToSvgPainter(Density(1f)), imageModifiers) // "xml" -> loader.loadData().decodeToImageVector() - else -> ComposeBitmapImageResource(loader.loadData().decodeToImageBitmap()) + else -> ComposeBitmapImageResource(loader.loadData(imageModifiers).decodeToImageBitmap()) } } else { error("Unsupported loader: $loader") @@ -58,16 +58,47 @@ val builder = documentBuilderFactory.newDocumentBuilder() val knownModifiers = modifiers as? DefaultImageModifiers val patcher = knownModifiers?.stroke?.let { stroke -> svgPatcher { - replace("fill", Color.Transparent.toHex()) - replace("stroke", stroke.toHex()) + for (color in backgroundPalette) { + replaceIfMatches("fill", color.toIconsColor().toHex(), "transparent") + } + for (color in strokeColors) { + replaceIfMatches("fill", color.toIconsColor().toHex(), stroke.toHex()) + } } } combineWith modifiers?.svgPatcher patcher?.patch(document.documentElement) - println("New SVG:") - println(document.writeToString()) return document.writeToString().toByteArray() } +private val backgroundPalette = + listOf( + Color(0xFFEBECF0), + Color(0xFFE7EFFD), + Color(0xFFDFF2E0), + Color(0xFFF2FCF3), + Color(0xFFFFE8E8), + Color(0xFFFFF5F5), + Color(0xFFFFF8E3), + Color(0xFFFFF4EB), + Color(0xFFEEE0FF), + ) + +private val strokeColors = + listOf( + Color(0xFF000000), + Color(0xFFFFFFFF), + Color(0xFF818594), + Color(0xFF6C707E), + Color(0xFF3574F0), + Color(0xFF5FB865), + Color(0xFFE35252), + Color(0xFFEB7171), + Color(0xFFE3AE4D), + Color(0xFFFCC75B), + Color(0xFFF28C35), + Color(0xFF955AE0), + ) + private fun SvgPatcher.patch(element: Element) { for (operation in operations) { when (operation.operation) { @@ -78,9 +109,12 @@ private fun SvgPatcher.patch(element: Element) { } SvgPatchOperation.Operation.Replace -> { if (operation.conditional) { - val matches = element.getAttribute(operation.attributeName) == operation.expectedValue + val matches = + element.getAttribute(operation.attributeName).equals(operation.expectedValue, ignoreCase = true) if (matches == !operation.negatedCondition) { element.setAttribute(operation.attributeName, operation.value!!) + } else { + println("Conditional replace failed: ${element.getAttribute(operation.attributeName)} expected: ${operation.expectedValue}") } } else if (element.hasAttribute(operation.attributeName)) { element.setAttribute(operation.attributeName, operation.value!!) diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ResourceImageIconLoader.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ResourceImageIconLoader.kt index ec94ee2a1cf67..a29e051bd0333 100644 --- a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ResourceImageIconLoader.kt +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ResourceImageIconLoader.kt @@ -3,13 +3,33 @@ package org.jetbrains.jewel.ui.icon import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.ImageResourceLoader +import org.jetbrains.icons.impl.rendering.DefaultImageModifiers +import org.jetbrains.icons.rendering.ImageModifiers import org.jetbrains.jewel.foundation.InternalJewelApi @InternalJewelApi @ApiStatus.Internal public class PathImageResourceLoader(public val path: String, public val classLoader: ClassLoader?) : ImageResourceLoader { - public fun loadData(): ByteArray { - if (classLoader != null) return classLoader.getResourceAsStream(path)!!.readBytes() - return ClassLoader.getSystemResourceAsStream(path)!!.readBytes() + public fun loadData(imageModifiers: ImageModifiers?): ByteArray { + val knownMods = imageModifiers as? DefaultImageModifiers + val finalPath = applyPathModifiers(path, knownMods) + val resourceStream = if (classLoader != null) { + classLoader.getResourceAsStream(finalPath) + } else ClassLoader.getSystemResourceAsStream(path) + return resourceStream?.readBytes() ?: error("Resource not found: $finalPath") + } + + private fun applyPathModifiers(path: String, modifiers: DefaultImageModifiers?): String { + if (modifiers == null) return path + return buildString { + append(path.substringBeforeLast('/', "")) + append('/') + append(path.substringBeforeLast('.').substringAfterLast('/')) + if (modifiers.isDark) { + append("_dark") + } + append('.') + append(path.substringAfterLast('.')) + } } } From f8e8d8cae8c83827947c977b71443e812d895a66 Mon Sep 17 00:00:00 2001 From: Jakub Senohrabek Date: Wed, 11 Feb 2026 15:41:31 +0100 Subject: [PATCH 4/9] IJPL-176416 New Icons API - rebase fixes GitOrigin-RevId: eb7961d74037d22dfe18266924710002c2c60c8c --- lib/BUILD.bazel | 7 ------- platform/icons-api/BUILD.bazel | 1 + platform/icons-api/legacy-icon-support/BUILD.bazel | 1 + platform/icons-api/rendering/BUILD.bazel | 1 + platform/icons-api/rendering/lowlevel/BUILD.bazel | 1 + platform/icons-impl/BUILD.bazel | 1 + platform/icons-impl/intellij/BUILD.bazel | 1 + platform/jewel/ide-laf-bridge/BUILD.bazel | 1 - .../intellij.platform.jewel.ideLafBridge.iml | 2 ++ platform/jewel/int-ui/int-ui-standalone/BUILD.bazel | 5 ++--- .../intellij.platform.jewel.intUi.standalone.iml | 3 +++ platform/jewel/ui/BUILD.bazel | 1 - .../jewel/ui/icon/ComposeImageResourceProvider.kt | 2 -- 13 files changed, 13 insertions(+), 14 deletions(-) diff --git a/lib/BUILD.bazel b/lib/BUILD.bazel index a944408e36768..2cc56113cdd4f 100644 --- a/lib/BUILD.bazel +++ b/lib/BUILD.bazel @@ -18743,13 +18743,6 @@ java_library( visibility = ["//visibility:public"] ) -jvm_import( - name = "platform-jewel-int_ui-standalone-jetbrains-kotlinx-serialization-core-jvm", - jar = "@org_jetbrains_kotlinx-kotlinx-serialization-core-jvm-1_8_1_http//file", - source_jar = "@org_jetbrains_kotlinx-kotlinx-serialization-core-jvm-1_8_1-sources_http//file", - visibility = ["//visibility:public"] -) - copy_file( name = "org.commonmark/commonmark-0.24.0.jar_copy", src = "@org_commonmark-commonmark-0_24_0_http//file", diff --git a/platform/icons-api/BUILD.bazel b/platform/icons-api/BUILD.bazel index 72ab58c56f3e4..18112f6e69043 100644 --- a/platform/icons-api/BUILD.bazel +++ b/platform/icons-api/BUILD.bazel @@ -11,6 +11,7 @@ create_kotlinc_options( "org.jetbrains.icons.api.InternalIconsApi", "org.jetbrains.icons.ExperimentalIconsApi", ], + progressive = False, x_x_language = [] ) diff --git a/platform/icons-api/legacy-icon-support/BUILD.bazel b/platform/icons-api/legacy-icon-support/BUILD.bazel index 5d2a02f1e0d7d..bfdd7e0067e59 100644 --- a/platform/icons-api/legacy-icon-support/BUILD.bazel +++ b/platform/icons-api/legacy-icon-support/BUILD.bazel @@ -7,6 +7,7 @@ create_kotlinc_options( jvm_target = "11", api_version = "2.2", language_version = "2.2", + progressive = False, x_x_language = [] ) diff --git a/platform/icons-api/rendering/BUILD.bazel b/platform/icons-api/rendering/BUILD.bazel index 5c7e60f3e5908..24aa4c9127bc7 100644 --- a/platform/icons-api/rendering/BUILD.bazel +++ b/platform/icons-api/rendering/BUILD.bazel @@ -11,6 +11,7 @@ create_kotlinc_options( "org.jetbrains.icons.api.InternalIconsApi", "org.jetbrains.icons.ExperimentalIconsApi", ], + progressive = False, x_x_language = [] ) diff --git a/platform/icons-api/rendering/lowlevel/BUILD.bazel b/platform/icons-api/rendering/lowlevel/BUILD.bazel index 3c83479e3fd61..0157cc35beec9 100644 --- a/platform/icons-api/rendering/lowlevel/BUILD.bazel +++ b/platform/icons-api/rendering/lowlevel/BUILD.bazel @@ -6,6 +6,7 @@ create_kotlinc_options( name = "custom_lowlevel", api_version = "2.2", language_version = "2.2", + progressive = False, x_x_language = [] ) diff --git a/platform/icons-impl/BUILD.bazel b/platform/icons-impl/BUILD.bazel index 0a781ef5209cc..3b73b2e5f39ed 100644 --- a/platform/icons-impl/BUILD.bazel +++ b/platform/icons-impl/BUILD.bazel @@ -12,6 +12,7 @@ create_kotlinc_options( "org.jetbrains.icons.ExperimentalIconsApi", "org.jetbrains.icons.InternalIconsApi", ], + progressive = False, x_x_language = [] ) diff --git a/platform/icons-impl/intellij/BUILD.bazel b/platform/icons-impl/intellij/BUILD.bazel index b01aef363d512..38acb2cbe35e0 100644 --- a/platform/icons-impl/intellij/BUILD.bazel +++ b/platform/icons-impl/intellij/BUILD.bazel @@ -12,6 +12,7 @@ create_kotlinc_options( "org.jetbrains.icons.ExperimentalIconsApi", "org.jetbrains.icons.InternalIconsApi", ], + progressive = False, x_x_language = [] ) diff --git a/platform/jewel/ide-laf-bridge/BUILD.bazel b/platform/jewel/ide-laf-bridge/BUILD.bazel index 9e0eb1d239ef9..3702d62b1f7be 100644 --- a/platform/jewel/ide-laf-bridge/BUILD.bazel +++ b/platform/jewel/ide-laf-bridge/BUILD.bazel @@ -103,7 +103,6 @@ jvm_library( "//plugins/textmate/plugin", "//plugins/textmate/plugin:plugin_test_lib", "@lib//:io-mockk-jvm", - "//python:python-community-impl", ], exports = [ "//platform/jewel/foundation", diff --git a/platform/jewel/ide-laf-bridge/intellij.platform.jewel.ideLafBridge.iml b/platform/jewel/ide-laf-bridge/intellij.platform.jewel.ideLafBridge.iml index 9e6e0162d2acd..5b3034e7f4b86 100644 --- a/platform/jewel/ide-laf-bridge/intellij.platform.jewel.ideLafBridge.iml +++ b/platform/jewel/ide-laf-bridge/intellij.platform.jewel.ideLafBridge.iml @@ -54,6 +54,8 @@ + + \ No newline at end of file diff --git a/platform/jewel/int-ui/int-ui-standalone/BUILD.bazel b/platform/jewel/int-ui/int-ui-standalone/BUILD.bazel index 6dfd3d9ef89e6..621ee56b7df25 100644 --- a/platform/jewel/int-ui/int-ui-standalone/BUILD.bazel +++ b/platform/jewel/int-ui/int-ui-standalone/BUILD.bazel @@ -40,10 +40,9 @@ jvm_library( "//libraries/compose-runtime-desktop", "//platform/jewel/foundation", "//libraries/jbr", - "//platform/icons-api/rendering", - "//platform/icons-impl", - "@lib//:platform-jewel-int_ui-standalone-jetbrains-kotlinx-serialization-core-jvm", "@lib//:jna", + "//platform/icons-api", + "//platform/icons-impl", ], plugins = ["@lib//:compose-plugin"] ) diff --git a/platform/jewel/int-ui/int-ui-standalone/intellij.platform.jewel.intUi.standalone.iml b/platform/jewel/int-ui/int-ui-standalone/intellij.platform.jewel.intUi.standalone.iml index 47c80e1a60419..5f8ec09e9cc7d 100644 --- a/platform/jewel/int-ui/int-ui-standalone/intellij.platform.jewel.intUi.standalone.iml +++ b/platform/jewel/int-ui/int-ui-standalone/intellij.platform.jewel.intUi.standalone.iml @@ -41,5 +41,8 @@ + + + \ No newline at end of file diff --git a/platform/jewel/ui/BUILD.bazel b/platform/jewel/ui/BUILD.bazel index 677048bf52540..eed36b581cc2c 100644 --- a/platform/jewel/ui/BUILD.bazel +++ b/platform/jewel/ui/BUILD.bazel @@ -10,7 +10,6 @@ create_kotlinc_options( "org.jetbrains.jewel.foundation.ExperimentalJewelApi", "org.jetbrains.jewel.foundation.InternalJewelApi", "org.jetbrains.icons.api.InternalIconsApi", - "org.jetbrains.icons.api.ExperimentalIconsApi", "org.jetbrains.icons.ExperimentalIconsApi", ], x_context_parameters = True, diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeImageResourceProvider.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeImageResourceProvider.kt index c6d955063ab57..554ddbd676834 100644 --- a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeImageResourceProvider.kt +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeImageResourceProvider.kt @@ -113,8 +113,6 @@ private fun SvgPatcher.patch(element: Element) { element.getAttribute(operation.attributeName).equals(operation.expectedValue, ignoreCase = true) if (matches == !operation.negatedCondition) { element.setAttribute(operation.attributeName, operation.value!!) - } else { - println("Conditional replace failed: ${element.getAttribute(operation.attributeName)} expected: ${operation.expectedValue}") } } else if (element.hasAttribute(operation.attributeName)) { element.setAttribute(operation.attributeName, operation.value!!) From b130a2e25d810b9966093d2691a136d4511c9fd4 Mon Sep 17 00:00:00 2001 From: Jakub Senohrabek Date: Thu, 12 Feb 2026 00:37:24 +0100 Subject: [PATCH 5/9] IJPL-176416 New Icons API - bazel fix GitOrigin-RevId: 91466a710fc92ab6ad80b3667cc3600a72601268 --- .idea/modules.xml | 1 - lib/BUILD.bazel | 15 ------------- lib/MODULE.bazel | 14 ------------- platform/icons-impl/intellij/BUILD.bazel | 4 ++-- .../intellij.platform.icons.impl.intellij.iml | 21 +------------------ 5 files changed, 3 insertions(+), 52 deletions(-) diff --git a/.idea/modules.xml b/.idea/modules.xml index d6b6f810443bc..2359db1250fe0 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -1245,7 +1245,6 @@ - diff --git a/lib/BUILD.bazel b/lib/BUILD.bazel index 2cc56113cdd4f..5842d59d8e682 100644 --- a/lib/BUILD.bazel +++ b/lib/BUILD.bazel @@ -18285,21 +18285,6 @@ java_library( visibility = ["//visibility:public"] ) -copy_file( - name = "org.jetbrains.kotlinx/kotlinx-serialization-core-jvm-1.8.1.jar_copy", - src = "@org_jetbrains_kotlinx-kotlinx-serialization-core-jvm-1_8_1_http//file", - out = "org.jetbrains.kotlinx/kotlinx-serialization-core-jvm-1.8.1.jar", - allow_symlink = True, - visibility = ["//visibility:public"] -) - -jvm_import( - name = "platform-icons-impl-intellij-jetbrains-kotlinx-serialization-core-jvm", - jar = "@org_jetbrains_kotlinx-kotlinx-serialization-core-jvm-1_8_1_http//file", - source_jar = "@org_jetbrains_kotlinx-kotlinx-serialization-core-jvm-1_8_1-sources_http//file", - visibility = ["//visibility:public"] -) - copy_file( name = "com.pngencoder/pngencoder-0.14.0.jar_copy", src = "@com_pngencoder-pngencoder-0_14_0_http//file", diff --git a/lib/MODULE.bazel b/lib/MODULE.bazel index eaf8106e17ced..9046885f766d2 100644 --- a/lib/MODULE.bazel +++ b/lib/MODULE.bazel @@ -14524,20 +14524,6 @@ http_file( downloaded_file_path = "dbus-java-core-4.2.1-sources.jar" ) -http_file( - name = "org_jetbrains_kotlinx-kotlinx-serialization-core-jvm-1_8_1_http", - url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-core-jvm/1.8.1/kotlinx-serialization-core-jvm-1.8.1.jar", - sha256 = "3565b6d4d789bf70683c45566944287fc1d8dc75c23d98bd87d01059cc76f2b3", - downloaded_file_path = "kotlinx-serialization-core-jvm-1.8.1.jar" -) - -http_file( - name = "org_jetbrains_kotlinx-kotlinx-serialization-core-jvm-1_8_1-sources_http", - url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-core-jvm/1.8.1/kotlinx-serialization-core-jvm-1.8.1-sources.jar", - sha256 = "d4a7221a0c2ccb47bc736340344b256506cf8a5a49882883f8f56385ae69a1bc", - downloaded_file_path = "kotlinx-serialization-core-jvm-1.8.1-sources.jar" -) - http_file( name = "com_pngencoder-pngencoder-0_14_0_http", url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/com/pngencoder/pngencoder/0.14.0/pngencoder-0.14.0.jar", diff --git a/platform/icons-impl/intellij/BUILD.bazel b/platform/icons-impl/intellij/BUILD.bazel index 38acb2cbe35e0..f0a801132b09f 100644 --- a/platform/icons-impl/intellij/BUILD.bazel +++ b/platform/icons-impl/intellij/BUILD.bazel @@ -40,11 +40,11 @@ jvm_library( "//platform/core-api:core", "//platform/platform-impl/rpc", "//fleet/util/core", - "@lib//:platform-icons-impl-intellij-jetbrains-kotlinx-serialization-core-jvm", "//platform/core-impl", "//platform/icons-api/legacy-icon-support", "//platform/platform-impl:ide-impl", "//platform/core-ui", + "//libraries/kotlinx/serialization/core", ] ) @@ -65,7 +65,6 @@ jvm_library( "//platform/core-api:core", "//platform/platform-impl/rpc", "//fleet/util/core", - "@lib//:platform-icons-impl-intellij-jetbrains-kotlinx-serialization-core-jvm", "//platform/core-impl", "//libraries/junit5", "//platform/testFramework/junit5", @@ -77,6 +76,7 @@ jvm_library( "@lib//:junit5", "@lib//:junit5Jupiter", "@lib//:assert_j", + "//libraries/kotlinx/serialization/core", ] ) ### auto-generated section `build intellij.platform.icons.impl.intellij` end diff --git a/platform/icons-impl/intellij/intellij.platform.icons.impl.intellij.iml b/platform/icons-impl/intellij/intellij.platform.icons.impl.intellij.iml index af444173212c7..fe7e0eb550e90 100644 --- a/platform/icons-impl/intellij/intellij.platform.icons.impl.intellij.iml +++ b/platform/icons-impl/intellij/intellij.platform.icons.impl.intellij.iml @@ -41,26 +41,6 @@ - - - - - - 3565b6d4d789bf70683c45566944287fc1d8dc75c23d98bd87d01059cc76f2b3 - - - - - - - - - - - - - - @@ -71,5 +51,6 @@ + \ No newline at end of file From a0fa527aa6026ea743ebfceaba6e32d7ef943edc Mon Sep 17 00:00:00 2001 From: Jakub Senohrabek Date: Thu, 12 Feb 2026 18:41:57 +0100 Subject: [PATCH 6/9] IJPL-176416 New Icons API - separated rendering & core in implementation modules GitOrigin-RevId: 21a95c9085658ac565ac8c23bc1b3b0748e8dc15 --- .idea/modules.xml | 2 + build/bazel-generated-file-list.txt | 2 + .../build/productLayout/CoreModuleSets.kt | 8 +- .../legacy-icon-support/build.gradle.kts | 25 +++++ ...j.platform.icons.api.legacyIconSupport.xml | 1 + .../icons/legacyIconSupport/SwingIcon.kt | 9 +- .../legacyIconSupport/SwingIconManager.kt | 22 ++++- ....platform.icons.api.rendering.lowlevel.xml | 1 + .../intellij.platform.icons.api.rendering.xml | 5 +- .../icons/rendering/ImageResource.kt | 4 +- .../icons/rendering/ImageResourceLoader.kt | 20 ++++ .../icons/rendering/ImageResourceProvider.kt | 4 +- .../resources/intellij.platform.icons.api.xml | 1 + .../jetbrains/icons/ImageResourceLoader.kt | 5 - .../jetbrains/icons/ImageResourceLocation.kt | 16 +++ .../jetbrains/icons/design/IconDesigner.kt | 4 +- platform/icons-impl/BUILD.bazel | 9 +- .../intellij.platform.icons.impl.iml | 3 - platform/icons-impl/intellij/BUILD.bazel | 4 - .../intellij.platform.icons.impl.intellij.iml | 2 - .../icons-impl/intellij/rendering/BUILD.bazel | 99 +++++++++++++++++++ ...platform.icons.impl.intellij.rendering.iml | 59 +++++++++++ ...platform.icons.impl.intellij.rendering.xml | 38 +++++++ .../impl/intellij/rendering/Convertors.kt | 0 .../intellij/rendering/IconUpdateService.kt | 0 .../rendering/ImageResourceLoaderExtension.kt | 29 ++++++ .../rendering/IntelliJIconRendererManager.kt | 4 +- .../IntelliJMutableIconUpdateFlowImpl.kt | 0 .../rendering/SwingIconLayerRegistration.kt | 0 .../rendering/SwingIconLayerRenderer.kt | 5 +- .../custom/CustomIconLayerRendererProvider.kt | 6 +- .../images/DataLoaderImageResourceHolder.kt | 27 +++++ .../rendering/images/IntelliJImageResource.kt | 41 ++++++++ .../images/IntelliJImageResourceProvider.kt | 21 ++++ .../images/LegacyIconImageResourceHolder.kt | 34 +++++++ .../images/ModuleImageResourceLoader.kt | 36 +++++++ .../images/SwingImageResourceHolder.kt | 41 ++++++++ .../intellij.platform.icons.impl.intellij.xml | 9 +- .../impl/intellij/IntelliJIconManager.kt | 32 ++---- .../intellij/ModuleImageResourceLocation.kt | 54 ++++++++++ .../custom/CustomIconLayerRegistration.kt | 2 +- .../custom/CustomImageResourceLoader.kt | 21 ---- .../intellij/design/IntelliJIconDesigner.kt | 8 +- .../rendering/IntelliJImageResourceLoader.kt | 85 ---------------- .../IntelliJImageResourceProvider.kt | 89 ----------------- platform/icons-impl/rendering/BUILD.bazel | 47 +++++++++ .../icons-impl/rendering/build.gradle.kts | 30 ++++++ ...intellij.platform.icons.impl.rendering.iml | 42 ++++++++ ...intellij.platform.icons.impl.rendering.xml | 13 +++ .../icons/impl/rendering/AwtImageResource.kt | 2 +- .../rendering/CachedGPUImageResourceHolder.kt | 0 .../CoroutineBasedMutableIconUpdateFlow.kt | 0 .../rendering/DefaultDynamicIconRenderer.kt | 0 .../impl/rendering/DefaultIconRenderer.kt | 5 +- .../rendering/DefaultIconRendererManager.kt | 6 +- .../impl/rendering/DefaultImageModifiers.kt | 0 .../rendering/DefaultImageResourceProvider.kt | 0 .../impl/rendering/DefaultSwingIconManager.kt | 11 +++ .../rendering/MutableIconUpdateFlowBase.kt | 0 .../icons/impl/rendering/SwingIcon.kt | 2 +- .../icons/impl/rendering/SwingPaintingApi.kt | 25 ++--- .../layers/AnimatedIconLayerRenderer.kt | 6 +- .../rendering/layers/DefaultLayerLayout.kt | 12 ++- .../rendering/layers/IconIconLayerRenderer.kt | 18 ++-- .../rendering}/layers/IconLayerManager.kt | 7 +- .../rendering/layers/IconLayerRenderer.kt | 0 .../layers/ImageIconLayerRenderer.kt | 19 ++-- .../impl/rendering/layers/LayerHelpers.kt | 0 .../layers/LayoutIconLayerRenderer.kt | 21 ++-- .../layers/ShapeIconLayerRenderer.kt | 0 .../modifiers/ColorFilterModifier.kt | 0 .../impl/rendering/modifiers/IconModifier.kt | 0 .../rendering/modifiers/MarginIconModifier.kt | 0 .../icons/impl/rendering/toAwtColor.kt | 0 .../intellij.platform.icons.impl.xml | 4 +- .../{rendering => }/IconAnimationFrame.kt | 8 +- .../design/DefaultIconAnimationDesigner.kt | 2 +- .../icons/impl/design/DefaultIconDesigner.kt | 4 +- .../icons/impl/layers/AnimatedIconLayer.kt | 2 +- .../icons/impl/layers/ImageIconLayer.kt | 6 +- .../icons/impl/layers/ModifierHelpers.kt | 7 +- .../impl/layers/NaivePolymorphicSerializer.kt | 77 --------------- .../com/intellij/ui/icons/CoreIconManager.kt | 6 ++ .../intellij.moduleSets.core.platform.xml | 3 +- .../int-ui/int-ui-standalone/BUILD.bazel | 1 + .../int-ui/int-ui-standalone/build.gradle.kts | 1 + ...tellij.platform.jewel.intUi.standalone.iml | 1 + .../standalone/icon/StandaloneIconDesigner.kt | 4 +- ...tellij.platform.jewel.intUi.standalone.xml | 7 +- platform/jewel/settings.gradle.kts | 4 + platform/jewel/ui/BUILD.bazel | 3 + platform/jewel/ui/build.gradle.kts | 1 + .../jewel/ui/intellij.platform.jewel.ui.iml | 3 +- .../ui/icon/ComposeImageResourceProvider.kt | 14 +-- .../jewel/ui/icon/ResourceImageIconLoader.kt | 5 +- platform/platform-impl/bootstrap/BUILD.bazel | 2 + .../intellij.platform.ide.bootstrap.iml | 2 + .../com/intellij/platform/ide/bootstrap/ui.kt | 14 ++- .../META-INF/intellij.moduleSets.core.ide.xml | 3 +- .../intellij.moduleSets.core.lang.xml | 8 +- .../intellij.moduleSets.essential.minimal.xml | 8 +- .../intellij.moduleSets.essential.xml | 8 +- .../intellij.moduleSets.ide.common.xml | 8 +- 103 files changed, 907 insertions(+), 462 deletions(-) create mode 100644 platform/icons-api/legacy-icon-support/build.gradle.kts create mode 100644 platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResourceLoader.kt delete mode 100644 platform/icons-api/src/org/jetbrains/icons/ImageResourceLoader.kt create mode 100644 platform/icons-api/src/org/jetbrains/icons/ImageResourceLocation.kt create mode 100644 platform/icons-impl/intellij/rendering/BUILD.bazel create mode 100644 platform/icons-impl/intellij/rendering/intellij.platform.icons.impl.intellij.rendering.iml create mode 100644 platform/icons-impl/intellij/rendering/resources/intellij.platform.icons.impl.intellij.rendering.xml rename platform/icons-impl/intellij/{ => rendering}/src/org/jetbrains/icons/impl/intellij/rendering/Convertors.kt (100%) rename platform/icons-impl/intellij/{ => rendering}/src/org/jetbrains/icons/impl/intellij/rendering/IconUpdateService.kt (100%) create mode 100644 platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/ImageResourceLoaderExtension.kt rename platform/icons-impl/intellij/{ => rendering}/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJIconRendererManager.kt (93%) rename platform/icons-impl/intellij/{ => rendering}/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJMutableIconUpdateFlowImpl.kt (100%) rename platform/icons-impl/intellij/{ => rendering}/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRegistration.kt (100%) rename platform/icons-impl/intellij/{ => rendering}/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRenderer.kt (95%) rename platform/icons-impl/intellij/{src/org/jetbrains/icons/impl/intellij => rendering/src/org/jetbrains/icons/impl/intellij/rendering}/custom/CustomIconLayerRendererProvider.kt (88%) create mode 100644 platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/DataLoaderImageResourceHolder.kt create mode 100644 platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/IntelliJImageResource.kt create mode 100644 platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/IntelliJImageResourceProvider.kt create mode 100644 platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/LegacyIconImageResourceHolder.kt create mode 100644 platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/ModuleImageResourceLoader.kt create mode 100644 platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/SwingImageResourceHolder.kt create mode 100644 platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/ModuleImageResourceLocation.kt delete mode 100644 platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomImageResourceLoader.kt delete mode 100644 platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJImageResourceLoader.kt delete mode 100644 platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJImageResourceProvider.kt create mode 100644 platform/icons-impl/rendering/BUILD.bazel create mode 100644 platform/icons-impl/rendering/build.gradle.kts create mode 100644 platform/icons-impl/rendering/intellij.platform.icons.impl.rendering.iml create mode 100644 platform/icons-impl/rendering/resources/intellij.platform.icons.impl.rendering.xml rename platform/icons-impl/{ => rendering}/src/org/jetbrains/icons/impl/rendering/AwtImageResource.kt (97%) rename platform/icons-impl/{ => rendering}/src/org/jetbrains/icons/impl/rendering/CachedGPUImageResourceHolder.kt (100%) rename platform/icons-impl/{ => rendering}/src/org/jetbrains/icons/impl/rendering/CoroutineBasedMutableIconUpdateFlow.kt (100%) rename platform/icons-impl/{ => rendering}/src/org/jetbrains/icons/impl/rendering/DefaultDynamicIconRenderer.kt (100%) rename platform/icons-impl/{ => rendering}/src/org/jetbrains/icons/impl/rendering/DefaultIconRenderer.kt (91%) rename platform/icons-impl/{ => rendering}/src/org/jetbrains/icons/impl/rendering/DefaultIconRendererManager.kt (90%) rename platform/icons-impl/{ => rendering}/src/org/jetbrains/icons/impl/rendering/DefaultImageModifiers.kt (100%) rename platform/icons-impl/{ => rendering}/src/org/jetbrains/icons/impl/rendering/DefaultImageResourceProvider.kt (100%) create mode 100644 platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultSwingIconManager.kt rename platform/icons-impl/{ => rendering}/src/org/jetbrains/icons/impl/rendering/MutableIconUpdateFlowBase.kt (100%) rename platform/icons-impl/{ => rendering}/src/org/jetbrains/icons/impl/rendering/SwingIcon.kt (96%) rename platform/icons-impl/{ => rendering}/src/org/jetbrains/icons/impl/rendering/SwingPaintingApi.kt (93%) rename platform/icons-impl/{ => rendering}/src/org/jetbrains/icons/impl/rendering/layers/AnimatedIconLayerRenderer.kt (94%) rename platform/icons-impl/{ => rendering}/src/org/jetbrains/icons/impl/rendering/layers/DefaultLayerLayout.kt (88%) rename platform/icons-impl/{ => rendering}/src/org/jetbrains/icons/impl/rendering/layers/IconIconLayerRenderer.kt (78%) rename platform/icons-impl/{src/org/jetbrains/icons/impl => rendering/src/org/jetbrains/icons/impl/rendering}/layers/IconLayerManager.kt (82%) rename platform/icons-impl/{ => rendering}/src/org/jetbrains/icons/impl/rendering/layers/IconLayerRenderer.kt (100%) rename platform/icons-impl/{ => rendering}/src/org/jetbrains/icons/impl/rendering/layers/ImageIconLayerRenderer.kt (79%) rename platform/icons-impl/{ => rendering}/src/org/jetbrains/icons/impl/rendering/layers/LayerHelpers.kt (100%) rename platform/icons-impl/{ => rendering}/src/org/jetbrains/icons/impl/rendering/layers/LayoutIconLayerRenderer.kt (89%) rename platform/icons-impl/{ => rendering}/src/org/jetbrains/icons/impl/rendering/layers/ShapeIconLayerRenderer.kt (100%) rename platform/icons-impl/{ => rendering}/src/org/jetbrains/icons/impl/rendering/modifiers/ColorFilterModifier.kt (100%) rename platform/icons-impl/{ => rendering}/src/org/jetbrains/icons/impl/rendering/modifiers/IconModifier.kt (100%) rename platform/icons-impl/{ => rendering}/src/org/jetbrains/icons/impl/rendering/modifiers/MarginIconModifier.kt (100%) rename platform/icons-impl/{ => rendering}/src/org/jetbrains/icons/impl/rendering/toAwtColor.kt (100%) rename platform/icons-impl/src/org/jetbrains/icons/impl/{rendering => }/IconAnimationFrame.kt (81%) delete mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/layers/NaivePolymorphicSerializer.kt diff --git a/.idea/modules.xml b/.idea/modules.xml index 2359db1250fe0..20131c1ab389d 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -1196,7 +1196,9 @@ + + diff --git a/build/bazel-generated-file-list.txt b/build/bazel-generated-file-list.txt index c06cbe7e2e0ee..1ebe254829fc4 100644 --- a/build/bazel-generated-file-list.txt +++ b/build/bazel-generated-file-list.txt @@ -626,6 +626,8 @@ platform/icons-api/rendering platform/icons-api/rendering/lowlevel platform/icons-impl platform/icons-impl/intellij +platform/icons-impl/intellij/rendering +platform/icons-impl/rendering platform/ide-core platform/ide-core-impl platform/ide-core/plugins diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CoreModuleSets.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CoreModuleSets.kt index 3b73276df633d..5b454351ffec0 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CoreModuleSets.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CoreModuleSets.kt @@ -273,7 +273,8 @@ object CoreModuleSets { embeddedModule("intellij.platform.icons.api") embeddedModule("intellij.platform.icons.api.rendering") embeddedModule("intellij.platform.icons.api.rendering.lowlevel") - embeddedModule("intellij.platform.icons.api.legacyIconSupport") + embeddedModule("intellij.platform.icons.impl") + embeddedModule("intellij.platform.icons.impl.intellij") // Include minimal RPC infrastructure AFTER core platform modules // (kernel depends on platform.core, so core must be available first) @@ -348,8 +349,9 @@ object CoreModuleSets { // IDE implementation (depends on lang.core, so must come after) embeddedModule("intellij.platform.ide.impl") - embeddedModule("intellij.platform.icons.impl") - embeddedModule("intellij.platform.icons.impl.intellij") + embeddedModule("intellij.platform.icons.api.legacyIconSupport") + embeddedModule("intellij.platform.icons.impl.rendering") + embeddedModule("intellij.platform.icons.impl.intellij.rendering") // Additional dependencies specific to lang.impl and ide.impl embeddedModule("intellij.platform.ide.concurrency") diff --git a/platform/icons-api/legacy-icon-support/build.gradle.kts b/platform/icons-api/legacy-icon-support/build.gradle.kts new file mode 100644 index 0000000000000..a9a9398a6c82f --- /dev/null +++ b/platform/icons-api/legacy-icon-support/build.gradle.kts @@ -0,0 +1,25 @@ +// This file is used by Jewel gradle script, check community/platform/jewel + +plugins { + jewel + alias(libs.plugins.kotlinx.serialization) +} + +sourceSets { + main { + kotlin { + setSrcDirs(listOf("src")) + } + } + + test { + kotlin { + setSrcDirs(listOf("test")) + } + } +} + +dependencies { + api(project(":jb-icons-api")) + api(libs.kotlinx.serialization.core) +} diff --git a/platform/icons-api/legacy-icon-support/resources/intellij.platform.icons.api.legacyIconSupport.xml b/platform/icons-api/legacy-icon-support/resources/intellij.platform.icons.api.legacyIconSupport.xml index fbe842d67cff4..eb960f7b3edea 100644 --- a/platform/icons-api/legacy-icon-support/resources/intellij.platform.icons.api.legacyIconSupport.xml +++ b/platform/icons-api/legacy-icon-support/resources/intellij.platform.icons.api.legacyIconSupport.xml @@ -2,6 +2,7 @@ + diff --git a/platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIcon.kt b/platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIcon.kt index 9991b9621641f..e940c067bd566 100644 --- a/platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIcon.kt +++ b/platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIcon.kt @@ -3,11 +3,9 @@ package org.jetbrains.icons.legacyIconSupport import org.jetbrains.icons.ExperimentalIconsApi import org.jetbrains.icons.Icon -import org.jetbrains.icons.IconManager import org.jetbrains.icons.design.IconDesigner import org.jetbrains.icons.icon import org.jetbrains.icons.modifiers.IconModifier -import org.jetbrains.icons.modifiers.fillMaxSize /** * Layer for embedding swing icons. @@ -33,10 +31,5 @@ fun javax.swing.Icon.toNewIcon(modifier: IconModifier = IconModifier): Icon { */ @ExperimentalIconsApi fun Icon.toSwingIcon(): javax.swing.Icon { - return swingIconManager().toSwingIcon(this) -} - -@ExperimentalIconsApi -private fun swingIconManager(): SwingIconManager { - return IconManager.getInstance() as? SwingIconManager ?: error("Current IconManager doesn't implement SwingIconManager") + return SwingIconManager.getInstance().toSwingIcon(this) } \ No newline at end of file diff --git a/platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIconManager.kt b/platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIconManager.kt index 14ae8fd1b3f97..904dadf8a4537 100644 --- a/platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIconManager.kt +++ b/platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIconManager.kt @@ -1,10 +1,28 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.legacyIconSupport +import org.jetbrains.icons.ExperimentalIconsApi import org.jetbrains.icons.Icon -import org.jetbrains.icons.design.IconDesigner -import org.jetbrains.icons.modifiers.IconModifier +import java.util.ServiceLoader +@OptIn(ExperimentalIconsApi::class) interface SwingIconManager { fun toSwingIcon(icon: Icon): javax.swing.Icon + + companion object { + @Volatile + private var instance: SwingIconManager? = null + + @JvmStatic + fun getInstance(): SwingIconManager = instance ?: loadFromSPI() + + private fun loadFromSPI(): SwingIconManager = + ServiceLoader.load(SwingIconManager::class.java).firstOrNull() + ?: error("IconManager instance is not set and there is no SPI service on classpath.") + + + fun activate(manager: SwingIconManager) { + instance = manager + } + } } \ No newline at end of file diff --git a/platform/icons-api/rendering/lowlevel/resources/intellij.platform.icons.api.rendering.lowlevel.xml b/platform/icons-api/rendering/lowlevel/resources/intellij.platform.icons.api.rendering.lowlevel.xml index 6191909f6f14d..41244161273f1 100644 --- a/platform/icons-api/rendering/lowlevel/resources/intellij.platform.icons.api.rendering.lowlevel.xml +++ b/platform/icons-api/rendering/lowlevel/resources/intellij.platform.icons.api.rendering.lowlevel.xml @@ -2,6 +2,7 @@ + diff --git a/platform/icons-api/rendering/resources/intellij.platform.icons.api.rendering.xml b/platform/icons-api/rendering/resources/intellij.platform.icons.api.rendering.xml index 15ecc662cf97d..4bff8c44a8f8f 100644 --- a/platform/icons-api/rendering/resources/intellij.platform.icons.api.rendering.xml +++ b/platform/icons-api/rendering/resources/intellij.platform.icons.api.rendering.xml @@ -1,6 +1,9 @@ - + + + + diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResource.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResource.kt index 64a42e85b08c7..0773bee9f6a4d 100644 --- a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResource.kt +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResource.kt @@ -2,14 +2,14 @@ package org.jetbrains.icons.rendering import org.jetbrains.icons.ExperimentalIconsApi -import org.jetbrains.icons.ImageResourceLoader +import org.jetbrains.icons.ImageResourceLocation import org.jetbrains.icons.InternalIconsApi import org.jetbrains.icons.filters.ColorFilter import org.jetbrains.icons.patchers.SvgPatcher @OptIn(InternalIconsApi::class) @ExperimentalIconsApi -fun imageResource(loader: ImageResourceLoader, imageModifiers: ImageModifiers? = null): ImageResource = ImageResourceProvider.getInstance().loadImage(loader, imageModifiers) +fun imageResource(loader: ImageResourceLocation, imageModifiers: ImageModifiers? = null): ImageResource = ImageResourceProvider.getInstance().loadImage(loader, imageModifiers) @ExperimentalIconsApi interface ImageModifiers { diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResourceLoader.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResourceLoader.kt new file mode 100644 index 0000000000000..92ce025303afe --- /dev/null +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResourceLoader.kt @@ -0,0 +1,20 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.rendering + +import org.jetbrains.icons.ImageResourceLocation + +interface GenericImageResourceLoader { + fun loadGenericImage(location: ImageResourceLocation, imageModifiers: ImageModifiers? = null): ImageResource? +} + +@Suppress("UNCHECKED_CAST") +interface ImageResourceLoader: GenericImageResourceLoader { + override fun loadGenericImage( + location: ImageResourceLocation, + imageModifiers: ImageModifiers?, + ): ImageResource? { + return loadImage(location as? TLocation ?: error("Unsupported image resource location."), imageModifiers) + } + + fun loadImage(location: TLocation, imageModifiers: ImageModifiers? = null): ImageResource? +} \ No newline at end of file diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResourceProvider.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResourceProvider.kt index 7c551afe8281f..28a1134fd46b4 100644 --- a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResourceProvider.kt +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResourceProvider.kt @@ -1,13 +1,13 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.rendering -import org.jetbrains.icons.ImageResourceLoader +import org.jetbrains.icons.ImageResourceLocation import org.jetbrains.icons.InternalIconsApi import java.util.ServiceLoader @InternalIconsApi interface ImageResourceProvider { - fun loadImage(loader: ImageResourceLoader, imageModifiers: ImageModifiers? = null): ImageResource + fun loadImage(location: ImageResourceLocation, imageModifiers: ImageModifiers? = null): ImageResource companion object { @Volatile diff --git a/platform/icons-api/resources/intellij.platform.icons.api.xml b/platform/icons-api/resources/intellij.platform.icons.api.xml index 43a84f880b838..ff776b84600f0 100644 --- a/platform/icons-api/resources/intellij.platform.icons.api.xml +++ b/platform/icons-api/resources/intellij.platform.icons.api.xml @@ -2,6 +2,7 @@ + diff --git a/platform/icons-api/src/org/jetbrains/icons/ImageResourceLoader.kt b/platform/icons-api/src/org/jetbrains/icons/ImageResourceLoader.kt deleted file mode 100644 index 00c68ad6bbd9b..0000000000000 --- a/platform/icons-api/src/org/jetbrains/icons/ImageResourceLoader.kt +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.jetbrains.icons - -@ExperimentalIconsApi -interface ImageResourceLoader \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/ImageResourceLocation.kt b/platform/icons-api/src/org/jetbrains/icons/ImageResourceLocation.kt new file mode 100644 index 0000000000000..2d095a8815805 --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/ImageResourceLocation.kt @@ -0,0 +1,16 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons + +/** + * Represents a place from which image resource can be loaded. + * + * For example: + * - path + * - pluginId + * - moduleId + * + * When creating new locations, ensure to register new ImageResourceLoader (rendering api) for the specific location. + * Check current implementation on how to register extensions. + */ +@ExperimentalIconsApi +interface ImageResourceLocation \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/design/IconDesigner.kt b/platform/icons-api/src/org/jetbrains/icons/design/IconDesigner.kt index 8353f7ae4ac38..39a6c3129d549 100644 --- a/platform/icons-api/src/org/jetbrains/icons/design/IconDesigner.kt +++ b/platform/icons-api/src/org/jetbrains/icons/design/IconDesigner.kt @@ -3,7 +3,7 @@ package org.jetbrains.icons.design import org.jetbrains.icons.ExperimentalIconsApi import org.jetbrains.icons.Icon -import org.jetbrains.icons.ImageResourceLoader +import org.jetbrains.icons.ImageResourceLocation import org.jetbrains.icons.layers.IconLayer import org.jetbrains.icons.modifiers.IconModifier import org.jetbrains.icons.modifiers.align @@ -17,7 +17,7 @@ import org.jetbrains.icons.modifiers.size */ @ExperimentalIconsApi interface IconDesigner { - fun image(resourceLoader: ImageResourceLoader, modifier: IconModifier = IconModifier) + fun image(resourceLoader: ImageResourceLocation, modifier: IconModifier = IconModifier) fun image(path: String, classLoader: ClassLoader? = null, modifier: IconModifier = IconModifier) fun icon(icon: Icon, modifier: IconModifier = IconModifier) fun box(modifier: IconModifier = IconModifier, builder: IconDesigner.() -> Unit) diff --git a/platform/icons-impl/BUILD.bazel b/platform/icons-impl/BUILD.bazel index 3b73b2e5f39ed..9a2c70ab2f46d 100644 --- a/platform/icons-impl/BUILD.bazel +++ b/platform/icons-impl/BUILD.bazel @@ -33,15 +33,8 @@ jvm_library( "@lib//:kotlin-stdlib", "//libraries/kotlinx/coroutines/core", "//platform/icons-api", - "//platform/icons-api/rendering", - "//platform/icons-api/rendering/lowlevel", "//libraries/kotlinx/serialization/core", - "//platform/icons-api/legacy-icon-support", ], - exports = [ - "//platform/icons-api", - "//platform/icons-api/rendering", - "//platform/icons-api/rendering/lowlevel", - ] + exports = ["//platform/icons-api"] ) ### auto-generated section `build intellij.platform.icons.impl` end \ No newline at end of file diff --git a/platform/icons-impl/intellij.platform.icons.impl.iml b/platform/icons-impl/intellij.platform.icons.impl.iml index 797c270a0b6ad..655489b54488a 100644 --- a/platform/icons-impl/intellij.platform.icons.impl.iml +++ b/platform/icons-impl/intellij.platform.icons.impl.iml @@ -33,10 +33,7 @@ - - - \ No newline at end of file diff --git a/platform/icons-impl/intellij/BUILD.bazel b/platform/icons-impl/intellij/BUILD.bazel index f0a801132b09f..dca6deff2212c 100644 --- a/platform/icons-impl/intellij/BUILD.bazel +++ b/platform/icons-impl/intellij/BUILD.bazel @@ -33,7 +33,6 @@ jvm_library( "@lib//:kotlin-stdlib", "//libraries/kotlinx/coroutines/core", "//platform/icons-api", - "//platform/icons-api/rendering", "//platform/icons-impl", "//platform/util", "//platform/util:util-ui", @@ -41,7 +40,6 @@ jvm_library( "//platform/platform-impl/rpc", "//fleet/util/core", "//platform/core-impl", - "//platform/icons-api/legacy-icon-support", "//platform/platform-impl:ide-impl", "//platform/core-ui", "//libraries/kotlinx/serialization/core", @@ -58,7 +56,6 @@ jvm_library( "@lib//:kotlin-stdlib", "//libraries/kotlinx/coroutines/core", "//platform/icons-api", - "//platform/icons-api/rendering", "//platform/icons-impl", "//platform/util", "//platform/util:util-ui", @@ -69,7 +66,6 @@ jvm_library( "//libraries/junit5", "//platform/testFramework/junit5", "//platform/testFramework/junit5:junit5_test_lib", - "//platform/icons-api/legacy-icon-support", "//platform/platform-impl:ide-impl", "//platform/core-ui", "//libraries/kotlinx/serialization/json", diff --git a/platform/icons-impl/intellij/intellij.platform.icons.impl.intellij.iml b/platform/icons-impl/intellij/intellij.platform.icons.impl.intellij.iml index fe7e0eb550e90..3cd17717a658f 100644 --- a/platform/icons-impl/intellij/intellij.platform.icons.impl.intellij.iml +++ b/platform/icons-impl/intellij/intellij.platform.icons.impl.intellij.iml @@ -34,7 +34,6 @@ - @@ -44,7 +43,6 @@ - diff --git a/platform/icons-impl/intellij/rendering/BUILD.bazel b/platform/icons-impl/intellij/rendering/BUILD.bazel new file mode 100644 index 0000000000000..2b96aa2f9848f --- /dev/null +++ b/platform/icons-impl/intellij/rendering/BUILD.bazel @@ -0,0 +1,99 @@ +### auto-generated section `build intellij.platform.icons.impl.intellij.rendering` start +load("//build:compiler-options.bzl", "create_kotlinc_options") +load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") + +create_kotlinc_options( + name = "custom_rendering", + api_version = "2.2", + language_version = "2.2", + opt_in = [ + "org.jetbrains.icons.api.ExperimentalIconsApi", + "org.jetbrains.icons.api.InternalIconsApi", + "org.jetbrains.icons.ExperimentalIconsApi", + "org.jetbrains.icons.InternalIconsApi", + ], + progressive = False, + x_x_language = [] +) + +resourcegroup( + name = "rendering_resources", + srcs = glob(["resources/**/*"]), + strip_prefix = "resources" +) + +jvm_library( + name = "rendering", + module_name = "intellij.platform.icons.impl.intellij.rendering", + visibility = ["//visibility:public"], + srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), + resources = [":rendering_resources"], + kotlinc_opts = ":custom_rendering", + deps = [ + "@lib//:kotlin-stdlib", + "//libraries/kotlinx/coroutines/core", + "//platform/icons-api", + "//platform/icons-api/rendering", + "//platform/icons-impl", + "//platform/icons-impl/rendering", + "//platform/icons-impl/intellij", + "//platform/util", + "//platform/util:util-ui", + "//platform/core-api:core", + "//platform/platform-impl/rpc", + "//fleet/util/core", + "//platform/core-impl", + "//platform/icons-api/legacy-icon-support", + "//platform/platform-impl:ide-impl", + "//platform/core-ui", + "//libraries/kotlinx/serialization/core", + "//platform/service-container", + ] +) + +jvm_library( + name = "rendering_test_lib", + visibility = ["//visibility:public"], + srcs = glob(["test/**/*.kt", "test/**/*.java", "test/**/*.form"], allow_empty = True), + kotlinc_opts = ":custom_rendering", + associates = [":rendering"], + deps = [ + "@lib//:kotlin-stdlib", + "//libraries/kotlinx/coroutines/core", + "//platform/icons-api", + "//platform/icons-api/rendering", + "//platform/icons-impl", + "//platform/icons-impl/rendering", + "//platform/icons-impl/intellij", + "//platform/icons-impl/intellij:intellij_test_lib", + "//platform/util", + "//platform/util:util-ui", + "//platform/core-api:core", + "//platform/platform-impl/rpc", + "//fleet/util/core", + "//platform/core-impl", + "//libraries/junit5", + "//platform/testFramework/junit5", + "//platform/testFramework/junit5:junit5_test_lib", + "//platform/icons-api/legacy-icon-support", + "//platform/platform-impl:ide-impl", + "//platform/core-ui", + "//libraries/kotlinx/serialization/json", + "@lib//:junit5", + "@lib//:junit5Jupiter", + "@lib//:assert_j", + "//libraries/kotlinx/serialization/core", + "//platform/service-container", + "//platform/service-container:service-container_test_lib", + ] +) +### auto-generated section `build intellij.platform.icons.impl.intellij.rendering` end + +### auto-generated section `test intellij.platform.icons.impl.intellij.rendering` start +load("@community//build:tests-options.bzl", "jps_test") + +jps_test( + name = "rendering_test", + runtime_deps = [":rendering_test_lib"] +) +### auto-generated section `test intellij.platform.icons.impl.intellij.rendering` end \ No newline at end of file diff --git a/platform/icons-impl/intellij/rendering/intellij.platform.icons.impl.intellij.rendering.iml b/platform/icons-impl/intellij/rendering/intellij.platform.icons.impl.intellij.rendering.iml new file mode 100644 index 0000000000000..7c69c5ae1241a --- /dev/null +++ b/platform/icons-impl/intellij/rendering/intellij.platform.icons.impl.intellij.rendering.iml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + $KOTLIN_BUNDLED$/lib/kotlinx-serialization-compiler-plugin.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/platform/icons-impl/intellij/rendering/resources/intellij.platform.icons.impl.intellij.rendering.xml b/platform/icons-impl/intellij/rendering/resources/intellij.platform.icons.impl.intellij.rendering.xml new file mode 100644 index 0000000000000..64c318c60fda7 --- /dev/null +++ b/platform/icons-impl/intellij/rendering/resources/intellij.platform.icons.impl.intellij.rendering.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/Convertors.kt b/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/Convertors.kt similarity index 100% rename from platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/Convertors.kt rename to platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/Convertors.kt diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IconUpdateService.kt b/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/IconUpdateService.kt similarity index 100% rename from platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IconUpdateService.kt rename to platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/IconUpdateService.kt diff --git a/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/ImageResourceLoaderExtension.kt b/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/ImageResourceLoaderExtension.kt new file mode 100644 index 0000000000000..9b0e753b39ada --- /dev/null +++ b/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/ImageResourceLoaderExtension.kt @@ -0,0 +1,29 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij.rendering + +import com.intellij.openapi.extensions.ExtensionPointName +import com.intellij.util.xmlb.annotations.Attribute +import org.jetbrains.icons.ImageResourceLocation +import org.jetbrains.icons.rendering.GenericImageResourceLoader + + +internal class ImageResourceLoaderExtension { + @Attribute("loader") + lateinit var loader: GenericImageResourceLoader + + @Attribute("location") + lateinit var location: String + + companion object { + fun getLoaderFor(location: ImageResourceLocation): GenericImageResourceLoader? { + for (extension in LOADER_EP_NAME.extensionList) { + if (location::class.qualifiedName == extension.location) { + return extension.loader + } + } + return null + } + + val LOADER_EP_NAME: ExtensionPointName = ExtensionPointName("com.intellij.icons.imageResourceLoader") + } +} diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJIconRendererManager.kt b/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJIconRendererManager.kt similarity index 93% rename from platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJIconRendererManager.kt rename to platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJIconRendererManager.kt index e066e7c6d4023..9123322f2b699 100644 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJIconRendererManager.kt +++ b/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJIconRendererManager.kt @@ -1,11 +1,11 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.impl.intellij.rendering import com.intellij.util.ui.StartupUiUtil import kotlinx.coroutines.CoroutineScope import org.jetbrains.icons.ExperimentalIconsApi import org.jetbrains.icons.InternalIconsApi -import org.jetbrains.icons.impl.intellij.custom.CustomIconLayerRendererProvider +import org.jetbrains.icons.impl.intellij.rendering.custom.CustomIconLayerRendererProvider import org.jetbrains.icons.rendering.ImageModifiers import org.jetbrains.icons.rendering.MutableIconUpdateFlow import org.jetbrains.icons.rendering.RenderingContext diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJMutableIconUpdateFlowImpl.kt b/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJMutableIconUpdateFlowImpl.kt similarity index 100% rename from platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJMutableIconUpdateFlowImpl.kt rename to platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJMutableIconUpdateFlowImpl.kt diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRegistration.kt b/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRegistration.kt similarity index 100% rename from platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRegistration.kt rename to platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRegistration.kt diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRenderer.kt b/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRenderer.kt similarity index 95% rename from platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRenderer.kt rename to platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRenderer.kt index b9201c06475bf..6f48b54e9e664 100644 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRenderer.kt +++ b/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRenderer.kt @@ -6,7 +6,7 @@ import com.intellij.ui.DeferredIcon import com.intellij.ui.DeferredIconListener import com.intellij.util.messages.impl.subscribeAsFlow import kotlinx.coroutines.launch -import org.jetbrains.icons.impl.intellij.custom.CustomIconLayerRendererProvider +import org.jetbrains.icons.impl.intellij.rendering.custom.CustomIconLayerRendererProvider import org.jetbrains.icons.impl.rendering.DefaultImageResourceProvider import org.jetbrains.icons.rendering.Dimensions import org.jetbrains.icons.rendering.ImageResource @@ -19,6 +19,7 @@ import org.jetbrains.icons.impl.rendering.layers.applyTo import org.jetbrains.icons.impl.rendering.layers.generateImageModifiers import org.jetbrains.icons.layers.IconLayer import org.jetbrains.icons.legacyIconSupport.SwingIconLayer +import javax.swing.Icon class SwingIconLayerRendererProvider: CustomIconLayerRendererProvider { override fun handles(layer: IconLayer): Boolean { @@ -47,7 +48,7 @@ class SwingIconLayerRenderer( if (layer.legacyIcon is DeferredIcon) { val flow = ApplicationManager.getApplication().messageBus.subscribeAsFlow(DeferredIconListener.TOPIC) { object : DeferredIconListener { - override fun evaluated(deferred: DeferredIcon, result: javax.swing.Icon) { + override fun evaluated(deferred: DeferredIcon, result: Icon) { if (deferred != icon) return trySend(result) } diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomIconLayerRendererProvider.kt b/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/custom/CustomIconLayerRendererProvider.kt similarity index 88% rename from platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomIconLayerRendererProvider.kt rename to platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/custom/CustomIconLayerRendererProvider.kt index 1141e0fb110cb..eb21db7f41819 100644 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomIconLayerRendererProvider.kt +++ b/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/custom/CustomIconLayerRendererProvider.kt @@ -1,10 +1,10 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.jetbrains.icons.impl.intellij.custom +package org.jetbrains.icons.impl.intellij.rendering.custom import com.intellij.openapi.extensions.ExtensionPointName import org.jetbrains.icons.impl.intellij.rendering.SwingIconLayerRenderer -import org.jetbrains.icons.layers.IconLayer import org.jetbrains.icons.impl.rendering.layers.IconLayerRenderer +import org.jetbrains.icons.layers.IconLayer import org.jetbrains.icons.legacyIconSupport.SwingIconLayer import org.jetbrains.icons.rendering.RenderingContext @@ -20,6 +20,6 @@ interface CustomIconLayerRendererProvider { return null } - val EP_NAME: ExtensionPointName = ExtensionPointName("com.intellij.customIconLayerRendererProvider") + val EP_NAME: ExtensionPointName = ExtensionPointName("com.intellij.icons.customIconLayerRendererProvider") } } \ No newline at end of file diff --git a/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/DataLoaderImageResourceHolder.kt b/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/DataLoaderImageResourceHolder.kt new file mode 100644 index 0000000000000..7f976592319cc --- /dev/null +++ b/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/DataLoaderImageResourceHolder.kt @@ -0,0 +1,27 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij.rendering.images + +import com.intellij.ui.icons.ImageDataLoader +import com.intellij.ui.scale.ScaleContext +import org.jetbrains.icons.rendering.Dimensions +import org.jetbrains.icons.rendering.ImageModifiers +import java.awt.Image + +internal class DataLoaderImageResourceHolder( + val dataLoader: ImageDataLoader +) : SwingImageResourceHolder { + override fun getImage( + scale: ScaleContext, + imageModifiers: ImageModifiers?, + ): Image? { + return dataLoader.loadImage( + parameters = imageModifiers.toLoadParameters(), + scaleContext = scale + ) + } + + override fun getExpectedDimensions(): Dimensions { + val img = getImage(ScaleContext.create(), null) ?: return Dimensions(0, 0) + return Dimensions(img.getWidth(null), img.getHeight(null)) + } +} diff --git a/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/IntelliJImageResource.kt b/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/IntelliJImageResource.kt new file mode 100644 index 0000000000000..ffb5aea7a2aa3 --- /dev/null +++ b/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/IntelliJImageResource.kt @@ -0,0 +1,41 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij.rendering.images + +import com.intellij.ui.scale.ScaleContext +import com.intellij.ui.scale.ScaleType +import com.intellij.util.JBHiDPIScaledImage +import org.jetbrains.icons.impl.rendering.AwtImageResource +import org.jetbrains.icons.impl.rendering.CachedGPUImageResourceHolder +import org.jetbrains.icons.rendering.BitmapImageResource +import org.jetbrains.icons.rendering.Bounds +import org.jetbrains.icons.rendering.EmptyBitmapImageResource +import org.jetbrains.icons.rendering.ImageModifiers +import org.jetbrains.icons.rendering.ImageScale +import org.jetbrains.icons.rendering.RescalableImageResource + +internal class IntelliJImageResource( + private val holder: SwingImageResourceHolder, + private val imageModifiers: ImageModifiers? = null +): RescalableImageResource, CachedGPUImageResourceHolder() { + private val expectedDimensions by lazy { holder.getExpectedDimensions() } + + override fun scale(scale: ImageScale): BitmapImageResource { + val objScale = scale.calculateScalingFactorByOriginalDimensions(width, height) + val scaleContext = ScaleContext.of(arrayOf( + ScaleType.OBJ_SCALE.of(objScale), + ScaleType.USR_SCALE.of(1.0), + ScaleType.SYS_SCALE.of(1.0), + )) + val rawImage = holder.getImage(scaleContext, imageModifiers) ?: return EmptyBitmapImageResource + val awtImage = (rawImage as? JBHiDPIScaledImage)?.delegate ?: rawImage + return AwtImageResource(awtImage) + } + + override fun calculateExpectedDimensions(scale: ImageScale): Bounds { + val ijScale = scale.calculateScalingFactorByOriginalDimensions(width, height) + return Bounds(0, 0, width = (width * ijScale).toInt(), height = (height * ijScale).toInt()) + } + + override val width: Int by lazy { expectedDimensions.width } + override val height: Int by lazy { expectedDimensions.height } +} diff --git a/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/IntelliJImageResourceProvider.kt b/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/IntelliJImageResourceProvider.kt new file mode 100644 index 0000000000000..bc004d5514260 --- /dev/null +++ b/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/IntelliJImageResourceProvider.kt @@ -0,0 +1,21 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij.rendering.images + +import org.jetbrains.icons.rendering.ImageModifiers +import org.jetbrains.icons.rendering.ImageResource +import org.jetbrains.icons.ImageResourceLocation +import org.jetbrains.icons.impl.intellij.rendering.ImageResourceLoaderExtension +import org.jetbrains.icons.impl.rendering.DefaultImageResourceProvider +import javax.swing.Icon + +class IntelliJImageResourceProvider: DefaultImageResourceProvider() { + override fun loadImage(location: ImageResourceLocation, imageModifiers: ImageModifiers?): ImageResource { + val loader = ImageResourceLoaderExtension.getLoaderFor(location) + if (loader == null) error("Cannot find loader for location: $location") + return loader.loadGenericImage(location, imageModifiers) ?: error("Cannot load image for location: $location") + } + + override fun fromSwingIcon(icon: Icon, imageModifiers: ImageModifiers?): ImageResource { + return IntelliJImageResource(LegacyIconImageResourceHolder(icon), imageModifiers) + } +} \ No newline at end of file diff --git a/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/LegacyIconImageResourceHolder.kt b/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/LegacyIconImageResourceHolder.kt new file mode 100644 index 0000000000000..86e23d39b2d1b --- /dev/null +++ b/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/LegacyIconImageResourceHolder.kt @@ -0,0 +1,34 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij.rendering.images + +import com.intellij.openapi.util.IconLoader +import com.intellij.ui.icons.CachedImageIcon +import com.intellij.ui.icons.RgbImageFilterSupplier +import com.intellij.ui.scale.ScaleContext +import org.jetbrains.icons.rendering.Dimensions +import org.jetbrains.icons.rendering.ImageModifiers +import java.awt.Image +import java.awt.image.RGBImageFilter +import javax.swing.Icon + +internal class LegacyIconImageResourceHolder( + private val backingLegacyIcon: Icon +): SwingImageResourceHolder { + override fun getExpectedDimensions(): Dimensions { + return Dimensions(backingLegacyIcon.iconWidth, backingLegacyIcon.iconHeight) + } + + override fun getImage(scale: ScaleContext, imageModifiers: ImageModifiers?): Image? { + val params = imageModifiers.toLoadParameters() + val svgPatcher = params.colorPatcher + val icon = if (backingLegacyIcon is CachedImageIcon && svgPatcher != null) { + backingLegacyIcon.createWithPatcher(svgPatcher, isDark = params.isDark, useStroke = params.isStroke) + } else backingLegacyIcon + val filtered = if (params.filters.isNotEmpty()) { + IconLoader.filterIcon(icon = icon, filterSupplier = object : RgbImageFilterSupplier { + override fun getFilter() = params.filters.first() as RGBImageFilter + }) + } else icon + return IconLoader.toImage(filtered, scale) + } +} \ No newline at end of file diff --git a/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/ModuleImageResourceLoader.kt b/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/ModuleImageResourceLoader.kt new file mode 100644 index 0000000000000..9bf0d22ccfac9 --- /dev/null +++ b/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/ModuleImageResourceLoader.kt @@ -0,0 +1,36 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij.rendering.images + +import com.intellij.ide.plugins.PluginManagerCore +import com.intellij.ide.plugins.contentModules +import com.intellij.openapi.extensions.PluginId +import com.intellij.openapi.util.IntellijInternalApi +import com.intellij.ui.icons.findIconLoaderByPath +import org.jetbrains.icons.impl.intellij.ModuleImageResourceLocation +import org.jetbrains.icons.rendering.Dimensions +import org.jetbrains.icons.rendering.ImageModifiers +import org.jetbrains.icons.rendering.ImageResource +import org.jetbrains.icons.rendering.ImageResourceLoader + +class ModuleImageResourceLoader: ImageResourceLoader { + override fun loadImage( + location: ModuleImageResourceLocation, + imageModifiers: ImageModifiers?, + ): ImageResource { + val classLoader = getClassLoader(location.pluginId, location.moduleId) + ?: error("Cannot recover classloader for plugin: ${location.pluginId} module: ${location.moduleId}") + val dataLoader = findIconLoaderByPath(location.path, classLoader) + return IntelliJImageResource(DataLoaderImageResourceHolder(dataLoader), imageModifiers) + } + + @OptIn(IntellijInternalApi::class) + private fun getClassLoader(pluginId: String, moduleId: String?): ClassLoader? { + val plugin = PluginManagerCore.findPlugin(PluginId.Companion.getId(pluginId)) ?: return null + if (moduleId == null) { + return plugin.classLoader + } + else { + return plugin.contentModules.firstOrNull { it.moduleId.name == moduleId }?.classLoader + } + } +} \ No newline at end of file diff --git a/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/SwingImageResourceHolder.kt b/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/SwingImageResourceHolder.kt new file mode 100644 index 0000000000000..47b7e4a7f7578 --- /dev/null +++ b/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/SwingImageResourceHolder.kt @@ -0,0 +1,41 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij.rendering.images + +import com.intellij.ui.icons.LoadIconParameters +import com.intellij.ui.scale.ScaleContext +import org.jetbrains.icons.impl.intellij.rendering.toAwtFilter +import org.jetbrains.icons.impl.intellij.rendering.toIJPatcher +import org.jetbrains.icons.impl.rendering.DefaultImageModifiers +import org.jetbrains.icons.modifiers.svgPatcher +import org.jetbrains.icons.patchers.combineWith +import org.jetbrains.icons.rendering.Dimensions +import org.jetbrains.icons.rendering.ImageModifiers +import java.awt.Image +import java.awt.image.ImageFilter + +internal interface SwingImageResourceHolder { + fun getImage(scale: ScaleContext, imageModifiers: ImageModifiers?): Image? + fun getExpectedDimensions(): Dimensions +} + +internal fun ImageModifiers?.toLoadParameters(): LoadIconParameters { + val filters = mutableListOf() + val colorFilter = this?.colorFilter + if (colorFilter != null) { + filters.add(colorFilter.toAwtFilter()) + } + val knownModifiers = this as? DefaultImageModifiers + val strokePatcher = knownModifiers?.stroke?.let { stroke -> + svgPatcher { + replace("fill", "transparent") + add("stroke", stroke.toHex()) + } + } + val colorPatcher = (this?.svgPatcher combineWith strokePatcher)?.toIJPatcher() + return LoadIconParameters( + filters = filters, + isDark = knownModifiers?.isDark ?: false, + colorPatcher = colorPatcher, + isStroke = knownModifiers?.stroke != null + ) +} \ No newline at end of file diff --git a/platform/icons-impl/intellij/resources/intellij.platform.icons.impl.intellij.xml b/platform/icons-impl/intellij/resources/intellij.platform.icons.impl.intellij.xml index 080b1e34279be..032a65a1ca438 100644 --- a/platform/icons-impl/intellij/resources/intellij.platform.icons.impl.intellij.xml +++ b/platform/icons-impl/intellij/resources/intellij.platform.icons.impl.intellij.xml @@ -3,30 +3,25 @@ + + - - - - - diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt index 05bce75162d5e..c35ca4feb1d00 100644 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt @@ -4,23 +4,14 @@ package org.jetbrains.icons.impl.intellij import kotlinx.serialization.modules.SerializersModuleBuilder import org.jetbrains.icons.DynamicIcon import org.jetbrains.icons.Icon +import org.jetbrains.icons.ImageResourceLocation import org.jetbrains.icons.design.IconDesigner -import org.jetbrains.icons.legacyIconSupport.SwingIconManager import org.jetbrains.icons.impl.DefaultIconManager import org.jetbrains.icons.impl.intellij.custom.CustomIconLayerRegistration -import org.jetbrains.icons.impl.intellij.custom.CustomImageResourceLoader import org.jetbrains.icons.impl.intellij.custom.CustomLegacyIconSerializer import org.jetbrains.icons.impl.intellij.design.IntelliJIconDesigner -import org.jetbrains.icons.impl.intellij.rendering.IntelliJIconRendererManager -import org.jetbrains.icons.impl.intellij.rendering.IntelliJImageResourceLoader -import org.jetbrains.icons.impl.intellij.rendering.IntelliJImageResourceLoaderSerializer -import org.jetbrains.icons.impl.intellij.rendering.IntelliJImageResourceProvider -import org.jetbrains.icons.impl.rendering.SwingIcon -import org.jetbrains.icons.rendering.IconRendererManager -import org.jetbrains.icons.ImageResourceLoader -import org.jetbrains.icons.rendering.ImageResourceProvider -class IntelliJIconManager : DefaultIconManager(), SwingIconManager { +class IntelliJIconManager : DefaultIconManager() { override fun icon(designer: IconDesigner.() -> Unit): Icon { val ijIconDesigner = IntelliJIconDesigner() ijIconDesigner.designer() @@ -34,22 +25,13 @@ class IntelliJIconManager : DefaultIconManager(), SwingIconManager { } } - override fun toSwingIcon(icon: Icon): javax.swing.Icon { - return SwingIcon(icon) - } - override fun SerializersModuleBuilder.buildCustomSerializers() { CustomLegacyIconSerializer.registerSerializersTo(this) - CustomImageResourceLoader.registerSerializersTo(this) CustomIconLayerRegistration.registerSerializersTo(this) - polymorphic(ImageResourceLoader::class, IntelliJImageResourceLoader::class, IntelliJImageResourceLoaderSerializer) - } - - companion object { - fun activate() { - org.jetbrains.icons.IconManager.activate(IntelliJIconManager()) - IconRendererManager.activate(IntelliJIconRendererManager()) - ImageResourceProvider.activate(IntelliJImageResourceProvider()) - } + polymorphic( + ImageResourceLocation::class, + ModuleImageResourceLocation::class, + ModuleImageResourceLocation.serializer() + ) } } \ No newline at end of file diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/ModuleImageResourceLocation.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/ModuleImageResourceLocation.kt new file mode 100644 index 0000000000000..8ddfbaa70e3a1 --- /dev/null +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/ModuleImageResourceLocation.kt @@ -0,0 +1,54 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij + +import com.intellij.ide.plugins.cl.PluginAwareClassLoader +import kotlinx.serialization.Serializable +import org.jetbrains.icons.ImageResourceLocation + +@Serializable +class ModuleImageResourceLocation( + @JvmField val path: String, + @JvmField val pluginId: String, + @JvmField val moduleId: String?, +): ImageResourceLocation { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ModuleImageResourceLocation + + if (path != other.path) return false + if (pluginId != other.pluginId) return false + if (moduleId != other.moduleId) return false + + return true + } + + override fun hashCode(): Int { + var result = path.hashCode() + result = 31 * result + pluginId.hashCode() + result = 31 * result + (moduleId?.hashCode() ?: 0) + return result + } + + override fun toString(): String { + return "ModuleImageResourceLoader(path='$path', pluginId='$pluginId', moduleId=$moduleId)" + } + + companion object { + fun fromClassLoader(path: String, classLoader: ClassLoader): ImageResourceLocation { + val (pluginId, moduleId) = getPluginAndModuleId(classLoader) + return ModuleImageResourceLocation(path, pluginId, moduleId) + } + + private fun getPluginAndModuleId(classLoader: ClassLoader): Pair { + if (classLoader is PluginAwareClassLoader) { + return classLoader.pluginId.idString to classLoader.moduleId + } + else { + return "com.intellij" to null + } + } + } + +} diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomIconLayerRegistration.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomIconLayerRegistration.kt index f1ff4fe37a8d3..06f55e831fddd 100644 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomIconLayerRegistration.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomIconLayerRegistration.kt @@ -9,7 +9,7 @@ abstract class CustomIconLayerRegistration( klass: KClass ): CustomSerializableRegistration(klass) { @ApiStatus.Internal - companion object: CustomSerializableRegistration.Companion>( + companion object: CustomSerializableRegistration.Companion>( IconLayer ::class, "com.intellij.customIconLayer" ) diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomImageResourceLoader.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomImageResourceLoader.kt deleted file mode 100644 index 798a4bc80ca01..0000000000000 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomImageResourceLoader.kt +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.jetbrains.icons.impl.intellij.custom - -import com.intellij.ui.scale.ScaleContext -import org.jetbrains.annotations.ApiStatus -import org.jetbrains.icons.ImageResourceLoader -import org.jetbrains.icons.rendering.ImageModifiers -import java.awt.Image -import kotlin.reflect.KClass - -abstract class CustomImageResourceLoader( - klass: KClass -): CustomSerializableRegistration(klass) { - abstract fun loadImage(loader: T, scale: ScaleContext, imageModifiers: ImageModifiers?): Image? - - @ApiStatus.Internal - companion object: CustomSerializableRegistration.Companion>( - ImageResourceLoader::class, - "com.intellij.customImageResourceLoader" - ) -} \ No newline at end of file diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/design/IntelliJIconDesigner.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/design/IntelliJIconDesigner.kt index 2f956ab21ec83..1230183d85db6 100644 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/design/IntelliJIconDesigner.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/design/IntelliJIconDesigner.kt @@ -1,16 +1,14 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.impl.intellij.design -import com.intellij.ui.icons.findIconLoaderByPath import org.jetbrains.icons.modifiers.IconModifier import org.jetbrains.icons.impl.design.DefaultIconDesigner -import org.jetbrains.icons.impl.intellij.rendering.IntelliJImageResourceLoader -import org.jetbrains.icons.layers.IconLayer +import org.jetbrains.icons.impl.intellij.ModuleImageResourceLocation class IntelliJIconDesigner: DefaultIconDesigner() { override fun image(path: String, classLoader: ClassLoader?, modifier: IconModifier) { - val loader = findIconLoaderByPath(path, classLoader ?: this.javaClass.classLoader) - image(IntelliJImageResourceLoader(loader), modifier) + if (classLoader == null) error("Specifying classloader for icon image is required in IntelliJ.") + image(ModuleImageResourceLocation.fromClassLoader(path, classLoader), modifier) } override fun createNestedDesigner(): DefaultIconDesigner { diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJImageResourceLoader.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJImageResourceLoader.kt deleted file mode 100644 index f62bd4bde73bd..0000000000000 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJImageResourceLoader.kt +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.jetbrains.icons.impl.intellij.rendering - -import com.intellij.ui.icons.ImageDataLoader -import com.intellij.ui.icons.ImageDataLoaderDescriptor -import com.intellij.ui.icons.LoadIconParameters -import com.intellij.ui.scale.ScaleContext -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import org.jetbrains.icons.impl.rendering.DefaultImageModifiers -import org.jetbrains.icons.modifiers.svgPatcher -import org.jetbrains.icons.patchers.combineWith -import org.jetbrains.icons.rendering.ImageModifiers -import org.jetbrains.icons.ImageResourceLoader -import java.awt.Image -import java.awt.image.ImageFilter - -abstract class BaseIntelliJImageResourceLoader: SwingImageResourceLoader { - protected fun generateLoadIconParameters(imageModifiers: ImageModifiers?): LoadIconParameters { - val filters = mutableListOf() - val colorFilter = imageModifiers?.colorFilter - if (colorFilter != null) { - filters.add(colorFilter.toAwtFilter()) - } - val knownModifiers = imageModifiers as? DefaultImageModifiers - val strokePatcher = knownModifiers?.stroke?.let { stroke -> - svgPatcher { - replace("fill", "transparent") - add("stroke", stroke.toHex()) - } - } - val colorPatcher = (imageModifiers?.svgPatcher combineWith strokePatcher)?.toIJPatcher() - return LoadIconParameters( - filters = filters, - isDark = knownModifiers?.isDark ?: false, - colorPatcher = colorPatcher, - isStroke = knownModifiers?.stroke != null - ) - } -} - -@Serializable(with = IntelliJImageResourceLoaderSerializer::class) -class IntelliJImageResourceLoader( - val dataLoader: ImageDataLoader -): BaseIntelliJImageResourceLoader() { - override fun getExpectedDimensions(): Pair { - val img = loadImage(ScaleContext.create()) ?: return 0 to 0 - return img.getWidth(null) to img.getHeight(null) - } - - override fun loadImage(scale: ScaleContext, imageModifiers: ImageModifiers?): Image? { - return dataLoader.loadImage( - parameters = generateLoadIconParameters(imageModifiers), - scaleContext = scale - ) - } -} - -interface SwingImageResourceLoader: ImageResourceLoader { - fun getExpectedDimensions(): Pair - fun loadImage(scale: ScaleContext, imageModifiers: ImageModifiers? = null): Image? -} - -object IntelliJImageResourceLoaderSerializer: KSerializer { - private val actualSerializer = IconLoaderDescriptorHolder.serializer() - - override val descriptor: SerialDescriptor = actualSerializer.descriptor - - override fun serialize(encoder: Encoder, value: IntelliJImageResourceLoader) { - actualSerializer.serialize(encoder, IconLoaderDescriptorHolder(value.dataLoader.serializeToByteArray())) - } - - override fun deserialize(decoder: Decoder): IntelliJImageResourceLoader { - val descriptor = actualSerializer.deserialize(decoder).descriptor - return IntelliJImageResourceLoader(descriptor?.createIcon() ?: error("Unable to restore data loader from descriptor")) - } -} - -@Serializable -class IconLoaderDescriptorHolder( - val descriptor: ImageDataLoaderDescriptor? -) \ No newline at end of file diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJImageResourceProvider.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJImageResourceProvider.kt deleted file mode 100644 index ad39da4d8fa70..0000000000000 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJImageResourceProvider.kt +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.jetbrains.icons.impl.intellij.rendering - -import com.intellij.openapi.util.IconLoader -import com.intellij.openapi.util.IconLoader.filterIcon -import com.intellij.ui.icons.CachedImageIcon -import com.intellij.ui.icons.RgbImageFilterSupplier -import com.intellij.ui.scale.ScaleContext -import com.intellij.ui.scale.ScaleType -import com.intellij.util.JBHiDPIScaledImage -import org.jetbrains.icons.rendering.BitmapImageResource -import org.jetbrains.icons.rendering.Bounds -import org.jetbrains.icons.rendering.EmptyBitmapImageResource -import org.jetbrains.icons.rendering.ImageModifiers -import org.jetbrains.icons.rendering.ImageResource -import org.jetbrains.icons.ImageResourceLoader -import org.jetbrains.icons.rendering.ImageScale -import org.jetbrains.icons.rendering.RescalableImageResource -import org.jetbrains.icons.impl.rendering.AwtImageResource -import org.jetbrains.icons.impl.rendering.CachedGPUImageResourceHolder -import org.jetbrains.icons.impl.rendering.DefaultImageResourceProvider -import java.awt.Image -import java.awt.image.RGBImageFilter -import javax.swing.Icon - -class IntelliJImageResourceProvider: DefaultImageResourceProvider() { - override fun loadImage(loader: ImageResourceLoader, imageModifiers: ImageModifiers?): ImageResource { - if (loader is SwingImageResourceLoader) { - return fromSwingLoader(loader, imageModifiers) - } else error("Unsupported loader: $loader") - } - - override fun fromSwingIcon(icon: Icon, imageModifiers: ImageModifiers?): ImageResource { - return IntelliJImageResource(LegacyIconImageResourceLoader(icon), imageModifiers) - } - - private fun fromSwingLoader(loader: SwingImageResourceLoader, imageModifiers: ImageModifiers? = null): ImageResource = - IntelliJImageResource(loader, imageModifiers) -} - -private class LegacyIconImageResourceLoader( - private val backingLegacyIcon: Icon -): BaseIntelliJImageResourceLoader() { - override fun getExpectedDimensions(): Pair { - return backingLegacyIcon.iconWidth to backingLegacyIcon.iconHeight - } - - override fun loadImage(scale: ScaleContext, imageModifiers: ImageModifiers?): Image? { - val params = generateLoadIconParameters(imageModifiers) - val svgPatcher = params.colorPatcher - val icon = if (backingLegacyIcon is CachedImageIcon && svgPatcher != null) { - backingLegacyIcon.createWithPatcher(svgPatcher, isDark = params.isDark, useStroke = params.isStroke) - } else backingLegacyIcon - val filtered = if (params.filters.isNotEmpty()) { - filterIcon(icon = icon, filterSupplier = object : RgbImageFilterSupplier { - override fun getFilter() = params.filters.first() as RGBImageFilter - }) - } else icon - return IconLoader.toImage(filtered, scale) - } -} - -internal class IntelliJImageResource( - private val loader: SwingImageResourceLoader, - private val imageModifiers: ImageModifiers? = null -): RescalableImageResource, CachedGPUImageResourceHolder() { - - override fun scale(scale: ImageScale): BitmapImageResource { - val (width, height) = loader.getExpectedDimensions() - val objScale = scale.calculateScalingFactorByOriginalDimensions(width, height) - val scaleContext = ScaleContext.of(arrayOf( - ScaleType.OBJ_SCALE.of(objScale), - ScaleType.USR_SCALE.of(1.0), - ScaleType.SYS_SCALE.of(1.0), - )) - val rawImage = loader.loadImage(scaleContext, imageModifiers) ?: return EmptyBitmapImageResource - val awtImage = (rawImage as? JBHiDPIScaledImage)?.delegate ?: rawImage - return AwtImageResource(awtImage) - } - - override fun calculateExpectedDimensions(scale: ImageScale): Bounds { - val (width, height) = loader.getExpectedDimensions() - val ijScale = scale.calculateScalingFactorByOriginalDimensions(width, height) - return Bounds(0, 0, width = (width * ijScale).toInt(), height = (height * ijScale).toInt()) - } - - override val width: Int = loader.getExpectedDimensions().first - override val height: Int = loader.getExpectedDimensions().second -} \ No newline at end of file diff --git a/platform/icons-impl/rendering/BUILD.bazel b/platform/icons-impl/rendering/BUILD.bazel new file mode 100644 index 0000000000000..5a2bd3194ff04 --- /dev/null +++ b/platform/icons-impl/rendering/BUILD.bazel @@ -0,0 +1,47 @@ +### auto-generated section `build intellij.platform.icons.impl.rendering` start +load("//build:compiler-options.bzl", "create_kotlinc_options") +load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") + +create_kotlinc_options( + name = "custom_rendering", + api_version = "2.2", + language_version = "2.2", + opt_in = [ + "org.jetbrains.icons.api.InternalIconsApi", + "org.jetbrains.icons.api.ExperimentalIconsApi", + "org.jetbrains.icons.ExperimentalIconsApi", + "org.jetbrains.icons.InternalIconsApi", + ], + progressive = False, + x_x_language = [] +) + +resourcegroup( + name = "rendering_resources", + srcs = glob(["resources/**/*"]), + strip_prefix = "resources" +) + +jvm_library( + name = "rendering", + module_name = "intellij.platform.icons.impl.rendering", + visibility = ["//visibility:public"], + srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), + resources = [":rendering_resources"], + kotlinc_opts = ":custom_rendering", + deps = [ + "@lib//:kotlin-stdlib", + "//libraries/kotlinx/coroutines/core", + "//platform/icons-impl", + "//platform/icons-api/rendering", + "//platform/icons-api/rendering/lowlevel", + "//libraries/kotlinx/serialization/core", + "//platform/icons-api/legacy-icon-support", + ], + exports = [ + "//platform/icons-impl", + "//platform/icons-api/rendering", + "//platform/icons-api/rendering/lowlevel", + ] +) +### auto-generated section `build intellij.platform.icons.impl.rendering` end \ No newline at end of file diff --git a/platform/icons-impl/rendering/build.gradle.kts b/platform/icons-impl/rendering/build.gradle.kts new file mode 100644 index 0000000000000..d4a80ad56002f --- /dev/null +++ b/platform/icons-impl/rendering/build.gradle.kts @@ -0,0 +1,30 @@ +// This file is used by Jewel gradle script, check community/platform/jewel + +plugins { + jewel + alias(libs.plugins.kotlinx.serialization) +} + +sourceSets { + main { + kotlin { + setSrcDirs(listOf("src")) + } + } + + test { + kotlin { + setSrcDirs(listOf("test")) + } + } +} + +dependencies { + api(project(":jb-icons-api")) + api(project(":jb-icons-api-rendering")) + api(project(":jb-icons-api-rendering-lowlevel")) + api(project(":jb-icons-impl")) + api(project(":jb-icons-legacy-icon-support")) + api(libs.kotlinx.serialization.core) + api(libs.kotlinx.coroutines.core) +} diff --git a/platform/icons-impl/rendering/intellij.platform.icons.impl.rendering.iml b/platform/icons-impl/rendering/intellij.platform.icons.impl.rendering.iml new file mode 100644 index 0000000000000..c40f038f26aad --- /dev/null +++ b/platform/icons-impl/rendering/intellij.platform.icons.impl.rendering.iml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + $KOTLIN_BUNDLED$/lib/kotlinx-serialization-compiler-plugin.jar + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/platform/icons-impl/rendering/resources/intellij.platform.icons.impl.rendering.xml b/platform/icons-impl/rendering/resources/intellij.platform.icons.impl.rendering.xml new file mode 100644 index 0000000000000..6b7a378ea48be --- /dev/null +++ b/platform/icons-impl/rendering/resources/intellij.platform.icons.impl.rendering.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/AwtImageResource.kt b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/AwtImageResource.kt similarity index 97% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/AwtImageResource.kt rename to platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/AwtImageResource.kt index 82fda97aa6671..1cf7e1ca98a31 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/AwtImageResource.kt +++ b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/AwtImageResource.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.impl.rendering import org.jetbrains.icons.rendering.BitmapImageResource diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/CachedGPUImageResourceHolder.kt b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/CachedGPUImageResourceHolder.kt similarity index 100% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/CachedGPUImageResourceHolder.kt rename to platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/CachedGPUImageResourceHolder.kt diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/CoroutineBasedMutableIconUpdateFlow.kt b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/CoroutineBasedMutableIconUpdateFlow.kt similarity index 100% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/CoroutineBasedMutableIconUpdateFlow.kt rename to platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/CoroutineBasedMutableIconUpdateFlow.kt diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultDynamicIconRenderer.kt b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultDynamicIconRenderer.kt similarity index 100% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultDynamicIconRenderer.kt rename to platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultDynamicIconRenderer.kt diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultIconRenderer.kt b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultIconRenderer.kt similarity index 91% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultIconRenderer.kt rename to platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultIconRenderer.kt index a0aba252585b9..7f67a23de54ea 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultIconRenderer.kt +++ b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultIconRenderer.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.impl.rendering import org.jetbrains.icons.Icon @@ -10,7 +10,8 @@ import org.jetbrains.icons.rendering.RenderingContext import org.jetbrains.icons.rendering.ScalingContext import org.jetbrains.icons.impl.DefaultLayeredIcon import org.jetbrains.icons.impl.rendering.layers.IconLayerRenderer -import org.jetbrains.icons.impl.layers.IconLayerManager +import org.jetbrains.icons.impl.rendering.layers.IconLayerManager +import kotlin.compareTo class DefaultIconRenderer( val iconInstance: DefaultLayeredIcon, diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultIconRendererManager.kt b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultIconRendererManager.kt similarity index 90% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultIconRendererManager.kt rename to platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultIconRendererManager.kt index 9a2d20f00f2f4..8d38fccf66238 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultIconRendererManager.kt +++ b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultIconRendererManager.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.impl.rendering import org.jetbrains.icons.Icon @@ -14,7 +14,7 @@ import org.jetbrains.icons.impl.rendering.layers.AnimatedIconLayerRenderer import org.jetbrains.icons.impl.rendering.layers.IconIconLayerRenderer import org.jetbrains.icons.layers.IconLayer import org.jetbrains.icons.impl.rendering.layers.IconLayerRenderer -import org.jetbrains.icons.impl.layers.IconLayerManager +import org.jetbrains.icons.impl.rendering.layers.IconLayerManager import org.jetbrains.icons.rendering.RenderingContext import org.jetbrains.icons.impl.DefaultDynamicIcon import org.jetbrains.icons.impl.layers.ShapeIconLayer @@ -33,7 +33,7 @@ abstract class DefaultIconRendererManager: IconRendererManager, IconLayerManager protected fun createRendererOrNull(icon: Icon, context: RenderingContext, loadingStrategy: LoadingStrategy): IconRenderer? { return when (icon) { - is DefaultLayeredIcon -> DefaultIconRenderer(icon, context, loadingStrategy) + is DefaultLayeredIcon -> _root_ide_package_.org.jetbrains.icons.impl.rendering.DefaultIconRenderer(icon, context, loadingStrategy) is DefaultDynamicIcon -> DefaultDynamicIconRenderer(icon, context, loadingStrategy) else -> null } diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultImageModifiers.kt b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultImageModifiers.kt similarity index 100% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultImageModifiers.kt rename to platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultImageModifiers.kt diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultImageResourceProvider.kt b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultImageResourceProvider.kt similarity index 100% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultImageResourceProvider.kt rename to platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultImageResourceProvider.kt diff --git a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultSwingIconManager.kt b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultSwingIconManager.kt new file mode 100644 index 0000000000000..a245d064037bd --- /dev/null +++ b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultSwingIconManager.kt @@ -0,0 +1,11 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.rendering + +import org.jetbrains.icons.legacyIconSupport.SwingIconManager +import javax.swing.Icon + +class DefaultSwingIconManager: SwingIconManager { + override fun toSwingIcon(icon: org.jetbrains.icons.Icon): Icon { + return SwingIcon(icon) + } +} diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/MutableIconUpdateFlowBase.kt b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/MutableIconUpdateFlowBase.kt similarity index 100% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/MutableIconUpdateFlowBase.kt rename to platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/MutableIconUpdateFlowBase.kt diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingIcon.kt b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/SwingIcon.kt similarity index 96% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingIcon.kt rename to platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/SwingIcon.kt index 75ca6221ee4d4..43d5723e85609 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingIcon.kt +++ b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/SwingIcon.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.impl.rendering import org.jetbrains.icons.Icon diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingPaintingApi.kt b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/SwingPaintingApi.kt similarity index 93% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingPaintingApi.kt rename to platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/SwingPaintingApi.kt index 6221af03e92ca..55138e5315e54 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingPaintingApi.kt +++ b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/SwingPaintingApi.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.impl.rendering import org.jetbrains.icons.design.Color @@ -19,6 +19,7 @@ import java.awt.Image import java.awt.RenderingHints import java.awt.geom.Ellipse2D import java.awt.geom.Rectangle2D +import kotlin.text.toInt class SwingPaintingApi( val c: Component?, @@ -46,17 +47,17 @@ class SwingPaintingApi( override fun getUsedBounds(): Bounds = bounds override fun withCustomContext(bounds: Bounds, overrideColorFilter: ColorFilter?): PaintingApi { - return SwingPaintingApi( - c, - g, - x, - y, - bounds.x, - bounds.y, - bounds.width, - bounds.height, - overrideColorFilter ?: this.overrideColorFilter, - scaling + return _root_ide_package_.org.jetbrains.icons.impl.rendering.SwingPaintingApi( + c, + g, + x, + y, + bounds.x, + bounds.y, + bounds.width, + bounds.height, + overrideColorFilter ?: this.overrideColorFilter, + scaling ) } diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/AnimatedIconLayerRenderer.kt b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/AnimatedIconLayerRenderer.kt similarity index 94% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/AnimatedIconLayerRenderer.kt rename to platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/AnimatedIconLayerRenderer.kt index 1c6349594e259..baf7e7d0b677b 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/AnimatedIconLayerRenderer.kt +++ b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/AnimatedIconLayerRenderer.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.impl.rendering.layers import org.jetbrains.icons.rendering.Bounds @@ -7,8 +7,8 @@ import org.jetbrains.icons.rendering.PaintingApi import org.jetbrains.icons.rendering.RenderingContext import org.jetbrains.icons.rendering.ScalingContext import org.jetbrains.icons.impl.layers.AnimatedIconLayer -import org.jetbrains.icons.impl.layers.IconLayerManager -import org.jetbrains.icons.impl.rendering.IconAnimationFrame +import org.jetbrains.icons.impl.rendering.layers.IconLayerManager +import org.jetbrains.icons.impl.IconAnimationFrame import org.jetbrains.icons.impl.rendering.modifiers.applyTo class AnimatedIconLayerRenderer( diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/DefaultLayerLayout.kt b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/DefaultLayerLayout.kt similarity index 88% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/DefaultLayerLayout.kt rename to platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/DefaultLayerLayout.kt index ab19f3e8eaf38..23388f299e8a0 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/DefaultLayerLayout.kt +++ b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/DefaultLayerLayout.kt @@ -1,9 +1,10 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.impl.rendering.layers import org.jetbrains.icons.design.Color import org.jetbrains.icons.filters.ColorFilter import org.jetbrains.icons.rendering.Bounds +import kotlin.hashCode interface LayerLayout { val layerBounds: Bounds @@ -78,6 +79,13 @@ open class DefaultLayerLayout( cutoutMargin: Float?, stroke: Color? ): DefaultLayerLayout { - return DefaultLayerLayout(layerBounds, parentBounds, colorFilter, alpha, cutoutMargin, stroke) + return _root_ide_package_.org.jetbrains.icons.impl.rendering.layers.DefaultLayerLayout( + layerBounds, + parentBounds, + colorFilter, + alpha, + cutoutMargin, + stroke + ) } } \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/IconIconLayerRenderer.kt b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/IconIconLayerRenderer.kt similarity index 78% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/IconIconLayerRenderer.kt rename to platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/IconIconLayerRenderer.kt index c4c734d89c59f..dc7cc4a914ea6 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/IconIconLayerRenderer.kt +++ b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/IconIconLayerRenderer.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.impl.rendering.layers import org.jetbrains.icons.rendering.Bounds @@ -17,14 +17,14 @@ class IconIconLayerRenderer( private val renderer = layer.icon.createRenderer(renderingContext.adjustTo(layer)) override fun render(api: PaintingApi) { - val layout = DefaultLayerLayout( - Bounds( - 0, - 0, - api.bounds.width, - api.bounds.height, - ), - api.bounds + val layout = _root_ide_package_.org.jetbrains.icons.impl.rendering.layers.DefaultLayerLayout( + Bounds( + 0, + 0, + api.bounds.width, + api.bounds.height, + ), + api.bounds ) val appliedLayout = layer.modifier.applyTo(layout, api.scaling) val boundApi = api.withCustomContext(appliedLayout.calculateFinalBounds()) diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/layers/IconLayerManager.kt b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/IconLayerManager.kt similarity index 82% rename from platform/icons-impl/src/org/jetbrains/icons/impl/layers/IconLayerManager.kt rename to platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/IconLayerManager.kt index 5812d2dcb2250..c03a6d15c9690 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/layers/IconLayerManager.kt +++ b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/IconLayerManager.kt @@ -1,9 +1,8 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.jetbrains.icons.impl.layers +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.rendering.layers -import org.jetbrains.icons.rendering.RenderingContext -import org.jetbrains.icons.impl.rendering.layers.IconLayerRenderer import org.jetbrains.icons.layers.IconLayer +import org.jetbrains.icons.rendering.RenderingContext interface IconLayerManager { fun createRenderer(layer: IconLayer, renderingContext: RenderingContext): IconLayerRenderer diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/IconLayerRenderer.kt b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/IconLayerRenderer.kt similarity index 100% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/IconLayerRenderer.kt rename to platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/IconLayerRenderer.kt diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/ImageIconLayerRenderer.kt b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/ImageIconLayerRenderer.kt similarity index 79% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/ImageIconLayerRenderer.kt rename to platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/ImageIconLayerRenderer.kt index c2ae36cee330c..b99d1cc6da62a 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/ImageIconLayerRenderer.kt +++ b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/ImageIconLayerRenderer.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.impl.rendering.layers import org.jetbrains.icons.rendering.Bounds @@ -10,6 +10,7 @@ import org.jetbrains.icons.rendering.ScalingContext import org.jetbrains.icons.rendering.imageResource import org.jetbrains.icons.layers.IconLayer import org.jetbrains.icons.impl.layers.ImageIconLayer +import org.jetbrains.icons.impl.rendering.layers.applyTo import org.jetbrains.icons.impl.rendering.modifiers.applyTo class ImageIconLayerRenderer( @@ -31,14 +32,14 @@ abstract class BaseImageIconLayerRenderer: IconLayerRenderer { val currentImage = image val w = api.scaling.applyTo(currentImage.width) val h = api.scaling.applyTo(currentImage.height) - val layout = DefaultLayerLayout( - Bounds( - 0, - 0, - api.bounds.width.coerceAtMost(w ?: Integer.MAX_VALUE), - api.bounds.height.coerceAtMost(h ?: Integer.MAX_VALUE) - ), - api.bounds + val layout = _root_ide_package_.org.jetbrains.icons.impl.rendering.layers.DefaultLayerLayout( + Bounds( + 0, + 0, + api.bounds.width.coerceAtMost(w ?: Integer.MAX_VALUE), + api.bounds.height.coerceAtMost(h ?: Integer.MAX_VALUE) + ), + api.bounds ) val appliedLayout = layer.modifier.applyTo(layout, api.scaling) val finalBounds = appliedLayout.calculateFinalBounds() diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/LayerHelpers.kt b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/LayerHelpers.kt similarity index 100% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/LayerHelpers.kt rename to platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/LayerHelpers.kt diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/LayoutIconLayerRenderer.kt b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/LayoutIconLayerRenderer.kt similarity index 89% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/LayoutIconLayerRenderer.kt rename to platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/LayoutIconLayerRenderer.kt index f3f84e7675bdc..8a89e99bd27ed 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/LayoutIconLayerRenderer.kt +++ b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/LayoutIconLayerRenderer.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.impl.rendering.layers import org.jetbrains.icons.rendering.Bounds @@ -6,10 +6,11 @@ import org.jetbrains.icons.rendering.Dimensions import org.jetbrains.icons.rendering.PaintingApi import org.jetbrains.icons.rendering.RenderingContext import org.jetbrains.icons.rendering.ScalingContext -import org.jetbrains.icons.impl.layers.IconLayerManager +import org.jetbrains.icons.impl.rendering.layers.IconLayerManager import org.jetbrains.icons.impl.layers.LayoutIconLayer import org.jetbrains.icons.impl.rendering.modifiers.applyTo import org.jetbrains.icons.impl.rendering.modifiers.asPixels +import kotlin.compareTo class LayoutIconLayerRenderer( private val layoutLayer: LayoutIconLayer, @@ -23,14 +24,14 @@ class LayoutIconLayerRenderer( } override fun render(api: PaintingApi) { - val layout = DefaultLayerLayout( - Bounds( - 0, - 0, - api.bounds.width, - api.bounds.height, - ), - api.bounds, + val layout = _root_ide_package_.org.jetbrains.icons.impl.rendering.layers.DefaultLayerLayout( + Bounds( + 0, + 0, + api.bounds.width, + api.bounds.height, + ), + api.bounds, ) val appliedLayout = layoutLayer.modifier.applyTo(layout, api.scaling) val finalBounds = appliedLayout.calculateFinalBounds() diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/ShapeIconLayerRenderer.kt b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/ShapeIconLayerRenderer.kt similarity index 100% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/ShapeIconLayerRenderer.kt rename to platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/ShapeIconLayerRenderer.kt diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/modifiers/ColorFilterModifier.kt b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/modifiers/ColorFilterModifier.kt similarity index 100% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/modifiers/ColorFilterModifier.kt rename to platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/modifiers/ColorFilterModifier.kt diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/modifiers/IconModifier.kt b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/modifiers/IconModifier.kt similarity index 100% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/modifiers/IconModifier.kt rename to platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/modifiers/IconModifier.kt diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/modifiers/MarginIconModifier.kt b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/modifiers/MarginIconModifier.kt similarity index 100% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/modifiers/MarginIconModifier.kt rename to platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/modifiers/MarginIconModifier.kt diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/toAwtColor.kt b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/toAwtColor.kt similarity index 100% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/toAwtColor.kt rename to platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/toAwtColor.kt diff --git a/platform/icons-impl/resources/intellij.platform.icons.impl.xml b/platform/icons-impl/resources/intellij.platform.icons.impl.xml index 12bb3204e3594..eb960f7b3edea 100644 --- a/platform/icons-impl/resources/intellij.platform.icons.impl.xml +++ b/platform/icons-impl/resources/intellij.platform.icons.impl.xml @@ -2,11 +2,9 @@ + - - - diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/IconAnimationFrame.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/IconAnimationFrame.kt similarity index 81% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/IconAnimationFrame.kt rename to platform/icons-impl/src/org/jetbrains/icons/impl/IconAnimationFrame.kt index 825afb2c6eca6..d7bc85c44cabb 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/IconAnimationFrame.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/IconAnimationFrame.kt @@ -1,13 +1,13 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.jetbrains.icons.impl.rendering +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl import kotlinx.serialization.Serializable import org.jetbrains.icons.layers.IconLayer @Serializable class IconAnimationFrame( - val layers: List, - val duration: Long + val layers: List, + val duration: Long ) { override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/design/DefaultIconAnimationDesigner.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/design/DefaultIconAnimationDesigner.kt index 07a66901f37c6..bd2c92a214676 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/design/DefaultIconAnimationDesigner.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/design/DefaultIconAnimationDesigner.kt @@ -3,7 +3,7 @@ package org.jetbrains.icons.impl.design import org.jetbrains.icons.design.IconAnimationDesigner import org.jetbrains.icons.design.IconDesigner -import org.jetbrains.icons.impl.rendering.IconAnimationFrame +import org.jetbrains.icons.impl.IconAnimationFrame class DefaultIconAnimationDesigner( val rootDesigner: DefaultIconDesigner diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/design/DefaultIconDesigner.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/design/DefaultIconDesigner.kt index 4d55b98a06b9d..00f5c71055abf 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/design/DefaultIconDesigner.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/design/DefaultIconDesigner.kt @@ -14,13 +14,13 @@ import org.jetbrains.icons.impl.layers.IconIconLayer import org.jetbrains.icons.layers.IconLayer import org.jetbrains.icons.impl.layers.ImageIconLayer import org.jetbrains.icons.impl.layers.LayoutIconLayer -import org.jetbrains.icons.ImageResourceLoader +import org.jetbrains.icons.ImageResourceLocation import org.jetbrains.icons.impl.layers.ShapeIconLayer abstract class DefaultIconDesigner: IconDesigner { private val layers = mutableListOf() - override fun image(resourceLoader: ImageResourceLoader, modifier: IconModifier) { + override fun image(resourceLoader: ImageResourceLocation, modifier: IconModifier) { layers.add(ImageIconLayer(resourceLoader, modifier)) } diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/layers/AnimatedIconLayer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/AnimatedIconLayer.kt index bf1cd02eb93c2..f8af43a9d736e 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/layers/AnimatedIconLayer.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/AnimatedIconLayer.kt @@ -3,7 +3,7 @@ package org.jetbrains.icons.impl.layers import kotlinx.serialization.Serializable import org.jetbrains.icons.modifiers.IconModifier -import org.jetbrains.icons.impl.rendering.IconAnimationFrame +import org.jetbrains.icons.impl.IconAnimationFrame import org.jetbrains.icons.layers.IconLayer @Serializable diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/layers/ImageIconLayer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/ImageIconLayer.kt index d885206cad0b6..40ec711a75ecb 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/layers/ImageIconLayer.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/ImageIconLayer.kt @@ -4,12 +4,12 @@ package org.jetbrains.icons.impl.layers import kotlinx.serialization.Serializable import org.jetbrains.icons.layers.IconLayer import org.jetbrains.icons.modifiers.IconModifier -import org.jetbrains.icons.ImageResourceLoader +import org.jetbrains.icons.ImageResourceLocation @Serializable class ImageIconLayer( - val loader: ImageResourceLoader, - override val modifier: IconModifier + val loader: ImageResourceLocation, + override val modifier: IconModifier ) : IconLayer { override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/layers/ModifierHelpers.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/ModifierHelpers.kt index 845607599120c..3c82c05d52059 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/layers/ModifierHelpers.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/ModifierHelpers.kt @@ -1,11 +1,13 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.impl.layers +import org.jetbrains.icons.InternalIconsApi import org.jetbrains.icons.layers.IconLayer import org.jetbrains.icons.modifiers.CombinedIconModifier import org.jetbrains.icons.modifiers.IconModifier -internal inline fun IconLayer.findModifier(): TModifier? { +@InternalIconsApi +inline fun IconLayer.findModifier(): TModifier? { var output: TModifier? = null traverseModifiers(modifier) { if (it is TModifier) { @@ -17,7 +19,8 @@ internal inline fun IconLayer.findModifier(): return output } -private fun traverseModifiers(modifier: IconModifier, traverser: (IconModifier) -> Boolean): Boolean { +@InternalIconsApi +fun traverseModifiers(modifier: IconModifier, traverser: (IconModifier) -> Boolean): Boolean { if (traverser(modifier)) { if (modifier is CombinedIconModifier) { if (traverseModifiers(modifier.other, traverser)) { diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/layers/NaivePolymorphicSerializer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/NaivePolymorphicSerializer.kt deleted file mode 100644 index 8c79f5f4703eb..0000000000000 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/layers/NaivePolymorphicSerializer.kt +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.jetbrains.icons.impl.layers - -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.InternalSerializationApi -import kotlinx.serialization.KSerializer -import kotlinx.serialization.builtins.serializer -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.descriptors.SerialKind -import kotlinx.serialization.descriptors.buildClassSerialDescriptor -import kotlinx.serialization.descriptors.buildSerialDescriptor -import kotlinx.serialization.encoding.CompositeDecoder -import kotlinx.serialization.encoding.CompositeEncoder -import kotlinx.serialization.encoding.decodeStructure -import kotlinx.serialization.encoding.encodeStructure - -abstract class NaivePolymorphicSerializer(private val name: String): KSerializer { - @OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class) - override val descriptor: SerialDescriptor by lazy(LazyThreadSafetyMode.PUBLICATION) { - buildClassSerialDescriptor(name) { - element("kind", String.serializer().descriptor) - element( - "value", - buildSerialDescriptor(name + "#Value", SerialKind.CONTEXTUAL) - ) - } - } - - @OptIn(ExperimentalSerializationApi::class) - override fun deserialize(decoder: kotlinx.serialization.encoding.Decoder): Type { - return decoder.decodeStructure(descriptor) { - var type: String? = null - var value: Any? = null - if (decodeSequentially()) { - return@decodeStructure decodeSequentially(this) - } - - mainLoop@ while (true) { - when (val index = decodeElementIndex(descriptor)) { - CompositeDecoder.DECODE_DONE -> { - break@mainLoop - } - 0 -> { - type = decodeStringElement(descriptor, index) - } - 1 -> { - type = requireNotNull(type) { "Cannot read polymorphic value before its type token" } - value = decodeValue(descriptor, index, type) - } - else -> throw _root_ide_package_.kotlinx.serialization.SerializationException( - "Invalid index in polymorphic deserialization of " + - (type ?: "unknown class") + - "\n Expected 0, 1 or DECODE_DONE(-1), but found $index" - ) - } - } - @Suppress("UNCHECKED_CAST") - requireNotNull(value) { "Value has not been read for type $type" } as Type - } - } - - private fun decodeSequentially(compositeDecoder: CompositeDecoder): Type { - val type = compositeDecoder.decodeStringElement(descriptor, 0) - return compositeDecoder.decodeValue(descriptor, 1, type) - } - - override fun serialize(encoder: kotlinx.serialization.encoding.Encoder, value: Type) { - encoder.encodeStructure(descriptor) { - encodeStringElement(descriptor, 0, getActualType(value)) - encodeValue(descriptor, 1, value) - } - } - - protected abstract fun getActualType(value: Type): String - protected abstract fun CompositeEncoder.encodeValue(descriptor: SerialDescriptor, index: Int, value: Type) - protected abstract fun CompositeDecoder.decodeValue(descriptor: SerialDescriptor, index: Int, type: String): Type -} \ No newline at end of file diff --git a/platform/ide-core-impl/src/com/intellij/ui/icons/CoreIconManager.kt b/platform/ide-core-impl/src/com/intellij/ui/icons/CoreIconManager.kt index 2cc8cc2e6a51b..5bf8abb38ee24 100644 --- a/platform/ide-core-impl/src/com/intellij/ui/icons/CoreIconManager.kt +++ b/platform/ide-core-impl/src/com/intellij/ui/icons/CoreIconManager.kt @@ -249,6 +249,9 @@ class CoreIconManager : IconManager, CoreAwareIconManager { return hasher.asLong } + /** + * Ensure to also change ModuleImageResourceLocation in icons-impl/intellij + */ override fun getPluginAndModuleId(classLoader: ClassLoader): Pair { if (classLoader is PluginAwareClassLoader) { return classLoader.pluginId.idString to classLoader.moduleId @@ -258,6 +261,9 @@ class CoreIconManager : IconManager, CoreAwareIconManager { } } + /** + * Ensure to also change ModuleImageResourceLoader in icons-impl/intellij/rendering + */ override fun getClassLoader(pluginId: String, moduleId: String?): ClassLoader? { val plugin = PluginManagerCore.findPlugin(PluginId.getId(pluginId)) ?: return null if (moduleId == null) { diff --git a/platform/ide-core/resources/META-INF/intellij.moduleSets.core.platform.xml b/platform/ide-core/resources/META-INF/intellij.moduleSets.core.platform.xml index d99c66bcd3a84..bc91f307dac07 100644 --- a/platform/ide-core/resources/META-INF/intellij.moduleSets.core.platform.xml +++ b/platform/ide-core/resources/META-INF/intellij.moduleSets.core.platform.xml @@ -133,7 +133,8 @@ - + + diff --git a/platform/jewel/int-ui/int-ui-standalone/BUILD.bazel b/platform/jewel/int-ui/int-ui-standalone/BUILD.bazel index 621ee56b7df25..9a514cf0de896 100644 --- a/platform/jewel/int-ui/int-ui-standalone/BUILD.bazel +++ b/platform/jewel/int-ui/int-ui-standalone/BUILD.bazel @@ -43,6 +43,7 @@ jvm_library( "@lib//:jna", "//platform/icons-api", "//platform/icons-impl", + "//platform/icons-impl/rendering", ], plugins = ["@lib//:compose-plugin"] ) diff --git a/platform/jewel/int-ui/int-ui-standalone/build.gradle.kts b/platform/jewel/int-ui/int-ui-standalone/build.gradle.kts index 226c8cb01a834..61d0e68c4e241 100644 --- a/platform/jewel/int-ui/int-ui-standalone/build.gradle.kts +++ b/platform/jewel/int-ui/int-ui-standalone/build.gradle.kts @@ -16,6 +16,7 @@ dependencies { api(project(":jb-icons-api")) api(project(":jb-icons-api-rendering")) api(project(":jb-icons-impl")) + api(project(":jb-icons-impl-rendering")) implementation(libs.jbr.api) implementation(libs.jna.core) } diff --git a/platform/jewel/int-ui/int-ui-standalone/intellij.platform.jewel.intUi.standalone.iml b/platform/jewel/int-ui/int-ui-standalone/intellij.platform.jewel.intUi.standalone.iml index 5f8ec09e9cc7d..2541a3c2ba967 100644 --- a/platform/jewel/int-ui/int-ui-standalone/intellij.platform.jewel.intUi.standalone.iml +++ b/platform/jewel/int-ui/int-ui-standalone/intellij.platform.jewel.intUi.standalone.iml @@ -44,5 +44,6 @@ + \ No newline at end of file diff --git a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconDesigner.kt b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconDesigner.kt index 1b5a97bc4e24e..66d7af7b6e99a 100644 --- a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconDesigner.kt +++ b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconDesigner.kt @@ -3,11 +3,11 @@ package org.jetbrains.jewel.intui.standalone.icon import org.jetbrains.icons.impl.design.DefaultIconDesigner import org.jetbrains.icons.modifiers.IconModifier -import org.jetbrains.jewel.ui.icon.PathImageResourceLoader +import org.jetbrains.jewel.ui.icon.PathImageResourceLocation internal class StandaloneIconDesigner : DefaultIconDesigner() { override fun image(path: String, classLoader: ClassLoader?, modifier: IconModifier) { - image(PathImageResourceLoader(path, classLoader), modifier) + image(PathImageResourceLocation(path, classLoader), modifier) } override fun createNestedDesigner(): StandaloneIconDesigner = StandaloneIconDesigner() diff --git a/platform/jewel/int-ui/int-ui-standalone/src/main/resources/intellij.platform.jewel.intUi.standalone.xml b/platform/jewel/int-ui/int-ui-standalone/src/main/resources/intellij.platform.jewel.intUi.standalone.xml index 038db731b91b5..0204e03d55ea2 100644 --- a/platform/jewel/int-ui/int-ui-standalone/src/main/resources/intellij.platform.jewel.intUi.standalone.xml +++ b/platform/jewel/int-ui/int-ui-standalone/src/main/resources/intellij.platform.jewel.intUi.standalone.xml @@ -1,6 +1,11 @@ - + + + + + + diff --git a/platform/jewel/settings.gradle.kts b/platform/jewel/settings.gradle.kts index 3b4249154c4b4..c37b16a17efe6 100644 --- a/platform/jewel/settings.gradle.kts +++ b/platform/jewel/settings.gradle.kts @@ -55,12 +55,16 @@ include( ":jb-icons-api-rendering", ":jb-icons-api-rendering-lowlevel", ":jb-icons-impl", + ":jb-icons-impl-rendering", + ":jb-icons-legacy-icon-support" ) project(":jb-icons-api").projectDir = file("../icons-api") project(":jb-icons-api-rendering").projectDir = file("../icons-api/rendering") project(":jb-icons-api-rendering-lowlevel").projectDir = file("../icons-api/rendering/lowlevel") +project(":jb-icons-legacy-icon-support").projectDir = file("../icons-api/legacy-icon-support") project(":jb-icons-impl").projectDir = file("../icons-impl") +project(":jb-icons-impl-rendering").projectDir = file("../icons-impl/rendering") develocity { diff --git a/platform/jewel/ui/BUILD.bazel b/platform/jewel/ui/BUILD.bazel index eed36b581cc2c..a6b78bf7a613b 100644 --- a/platform/jewel/ui/BUILD.bazel +++ b/platform/jewel/ui/BUILD.bazel @@ -11,6 +11,7 @@ create_kotlinc_options( "org.jetbrains.jewel.foundation.InternalJewelApi", "org.jetbrains.icons.api.InternalIconsApi", "org.jetbrains.icons.ExperimentalIconsApi", + "org.jetbrains.icons.InternalIconsApi", ], x_context_parameters = True, x_explicit_api_mode = "strict" @@ -49,6 +50,7 @@ jvm_library( "//platform/icons-api/rendering", "//platform/icons-api/rendering/lowlevel", "//platform/icons-impl", + "//platform/icons-impl/rendering", ], exports = [ "//platform/jewel/foundation", @@ -89,6 +91,7 @@ jvm_library( "//platform/icons-api/rendering", "//platform/icons-api/rendering/lowlevel", "//platform/icons-impl", + "//platform/icons-impl/rendering", "//libraries/compose-foundation-desktop-junit", "//libraries/compose-foundation-desktop-junit:compose-foundation-desktop-junit_test_lib", ], diff --git a/platform/jewel/ui/build.gradle.kts b/platform/jewel/ui/build.gradle.kts index da203d2afcfaf..9b7348fce3c37 100644 --- a/platform/jewel/ui/build.gradle.kts +++ b/platform/jewel/ui/build.gradle.kts @@ -17,6 +17,7 @@ dependencies { api(project(":jb-icons-api-rendering")) api(project(":jb-icons-api-rendering-lowlevel")) api(project(":jb-icons-impl")) + api(project(":jb-icons-impl-rendering")) implementation(compose.components.resources) testImplementation(compose.desktop.uiTestJUnit4) testImplementation(compose.desktop.currentOs) { exclude(group = "org.jetbrains.compose.material") } diff --git a/platform/jewel/ui/intellij.platform.jewel.ui.iml b/platform/jewel/ui/intellij.platform.jewel.ui.iml index 2feb289443520..e39a2bb8ef8a2 100644 --- a/platform/jewel/ui/intellij.platform.jewel.ui.iml +++ b/platform/jewel/ui/intellij.platform.jewel.ui.iml @@ -4,7 +4,7 @@ - @@ -86,6 +86,7 @@ + \ No newline at end of file diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeImageResourceProvider.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeImageResourceProvider.kt index 554ddbd676834..7bff889bdd8d7 100644 --- a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeImageResourceProvider.kt +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeImageResourceProvider.kt @@ -18,7 +18,7 @@ import org.jetbrains.icons.patchers.SvgPatcher import org.jetbrains.icons.patchers.combineWith import org.jetbrains.icons.rendering.ImageModifiers import org.jetbrains.icons.rendering.ImageResource -import org.jetbrains.icons.ImageResourceLoader +import org.jetbrains.icons.ImageResourceLocation import org.jetbrains.icons.rendering.ImageResourceProvider import org.jetbrains.icons.impl.rendering.DefaultImageModifiers import org.jetbrains.jewel.foundation.InternalJewelApi @@ -30,19 +30,19 @@ import org.w3c.dom.Element @ApiStatus.Internal @InternalIconsApi public class ComposeImageResourceProvider : ImageResourceProvider { - override fun loadImage(loader: ImageResourceLoader, imageModifiers: ImageModifiers?): ImageResource { + override fun loadImage(location: ImageResourceLocation, imageModifiers: ImageModifiers?): ImageResource { // TODO Support image modifiers - if (loader is PathImageResourceLoader) { - val extension = loader.path.substringAfterLast(".").lowercase() - val data = loader.loadData(imageModifiers) + if (location is PathImageResourceLocation) { + val extension = location.path.substringAfterLast(".").lowercase() + val data = location.loadData(imageModifiers) val stream = ByteArrayInputStream(data) return when (extension) { "svg" -> ComposePainterImageResource(patchSvg(imageModifiers, stream).decodeToSvgPainter(Density(1f)), imageModifiers) // "xml" -> loader.loadData().decodeToImageVector() - else -> ComposeBitmapImageResource(loader.loadData(imageModifiers).decodeToImageBitmap()) + else -> ComposeBitmapImageResource(location.loadData(imageModifiers).decodeToImageBitmap()) } } else { - error("Unsupported loader: $loader") + error("Unsupported loader: $location") } } } diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ResourceImageIconLoader.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ResourceImageIconLoader.kt index a29e051bd0333..878b78ab6eccd 100644 --- a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ResourceImageIconLoader.kt +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ResourceImageIconLoader.kt @@ -2,14 +2,15 @@ package org.jetbrains.jewel.ui.icon import org.jetbrains.annotations.ApiStatus -import org.jetbrains.icons.ImageResourceLoader +import org.jetbrains.icons.ImageResourceLocation import org.jetbrains.icons.impl.rendering.DefaultImageModifiers import org.jetbrains.icons.rendering.ImageModifiers import org.jetbrains.jewel.foundation.InternalJewelApi +// TODO: Replace with ModuleImageResourceLocation and custom Loader for it @InternalJewelApi @ApiStatus.Internal -public class PathImageResourceLoader(public val path: String, public val classLoader: ClassLoader?) : ImageResourceLoader { +public class PathImageResourceLocation(public val path: String, public val classLoader: ClassLoader?) : ImageResourceLocation { public fun loadData(imageModifiers: ImageModifiers?): ByteArray { val knownMods = imageModifiers as? DefaultImageModifiers val finalPath = applyPathModifiers(path, knownMods) diff --git a/platform/platform-impl/bootstrap/BUILD.bazel b/platform/platform-impl/bootstrap/BUILD.bazel index dec24e1789086..411adbc14789e 100644 --- a/platform/platform-impl/bootstrap/BUILD.bazel +++ b/platform/platform-impl/bootstrap/BUILD.bazel @@ -48,6 +48,8 @@ jvm_library( "//platform/eel-provider", "//platform/icons-impl", "//platform/icons-impl/intellij", + "//platform/icons-impl/rendering", + "//platform/icons-impl/intellij/rendering", ] ) diff --git a/platform/platform-impl/bootstrap/intellij.platform.ide.bootstrap.iml b/platform/platform-impl/bootstrap/intellij.platform.ide.bootstrap.iml index 59decad7ed82e..f80d0dbab4550 100644 --- a/platform/platform-impl/bootstrap/intellij.platform.ide.bootstrap.iml +++ b/platform/platform-impl/bootstrap/intellij.platform.ide.bootstrap.iml @@ -48,5 +48,7 @@ + + \ No newline at end of file diff --git a/platform/platform-impl/bootstrap/src/com/intellij/platform/ide/bootstrap/ui.kt b/platform/platform-impl/bootstrap/src/com/intellij/platform/ide/bootstrap/ui.kt index 3c1bb38df94de..22423c3d977e9 100644 --- a/platform/platform-impl/bootstrap/src/com/intellij/platform/ide/bootstrap/ui.kt +++ b/platform/platform-impl/bootstrap/src/com/intellij/platform/ide/bootstrap/ui.kt @@ -35,11 +35,14 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.jetbrains.annotations.VisibleForTesting import org.jetbrains.icons.ExperimentalIconsApi -import org.jetbrains.icons.rendering.ImageResourceProvider +import org.jetbrains.icons.InternalIconsApi import org.jetbrains.icons.impl.intellij.IntelliJIconManager import org.jetbrains.icons.impl.intellij.rendering.IntelliJIconRendererManager -import org.jetbrains.icons.impl.intellij.rendering.IntelliJImageResourceProvider +import org.jetbrains.icons.impl.intellij.rendering.images.IntelliJImageResourceProvider +import org.jetbrains.icons.impl.rendering.DefaultSwingIconManager +import org.jetbrains.icons.legacyIconSupport.SwingIconManager import org.jetbrains.icons.rendering.IconRendererManager +import org.jetbrains.icons.rendering.ImageResourceProvider import java.awt.Font import java.awt.GraphicsEnvironment import java.awt.Toolkit @@ -50,7 +53,7 @@ import javax.swing.RepaintManager import javax.swing.UIManager import kotlin.system.exitProcess -@OptIn(ExperimentalIconsApi::class) +@OptIn(ExperimentalIconsApi::class, InternalIconsApi::class) internal suspend fun initUi(initAwtToolkitJob: Job, isHeadless: Boolean, asyncScope: CoroutineScope) { // IdeaLaF uses AllIcons - icon manager must be activated if (!isHeadless) { @@ -59,7 +62,10 @@ internal suspend fun initUi(initAwtToolkitJob: Job, isHeadless: Boolean, asyncSc IconManager.activate(iconManager) } span("new icon manager activation") { - IntelliJIconManager.activate() + org.jetbrains.icons.IconManager.activate(IntelliJIconManager()) + IconRendererManager.activate(IntelliJIconRendererManager()) + ImageResourceProvider.activate(IntelliJImageResourceProvider()) + SwingIconManager.activate(DefaultSwingIconManager()) } } diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.ide.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.ide.xml index 236f7f0147690..7d9b8681ab1d6 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.ide.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.ide.xml @@ -134,7 +134,8 @@ - + + diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.lang.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.lang.xml index 6baf3aa3fb869..e533aa0bc95f0 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.lang.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.lang.xml @@ -135,7 +135,8 @@ - + + @@ -173,8 +174,9 @@ - - + + + diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.minimal.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.minimal.xml index 3a9f674014ea4..fcc5009c6c897 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.minimal.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.minimal.xml @@ -136,7 +136,8 @@ - + + @@ -174,8 +175,9 @@ - - + + + diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.xml index 09de132fcb264..f3bd2b794b2e9 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.xml @@ -137,7 +137,8 @@ - + + @@ -175,8 +176,9 @@ - - + + + diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.ide.common.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.ide.common.xml index fcb308d0c41ad..41c71efadea4b 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.ide.common.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.ide.common.xml @@ -140,7 +140,8 @@ - + + @@ -178,8 +179,9 @@ - - + + + From 4fcca8289e9b12fa4de8b453da501733f2e6ad1d Mon Sep 17 00:00:00 2001 From: Jakub Senohrabek Date: Mon, 16 Feb 2026 14:20:31 +0100 Subject: [PATCH 7/9] IJPL-176416 New Icons API - final touches before merge GitOrigin-RevId: b36c12cfd47a204d2af7352d9d0db849164a65f4 --- .idea/modules.xml | 4 - build/bazel-generated-file-list.txt | 4 - .../build/productLayout/CoreModuleSets.kt | 8 +- platform/icons-api/BUILD.bazel | 6 +- platform/icons-api/build.gradle.kts | 1 + .../icons-api/intellij.platform.icons.api.iml | 3 +- .../icons-api/legacy-icon-support/BUILD.bazel | 34 ------- .../icons-api/legacy-icon-support/README.md | 22 ----- .../legacy-icon-support/build.gradle.kts | 25 ----- ...j.platform.icons.api.legacyIconSupport.iml | 38 ------- .../legacy-icon-support/module-content.yaml | 3 - ...j.platform.icons.api.legacyIconSupport.xml | 10 -- .../legacyIconSupport/SwingIconManager.kt | 28 ------ platform/icons-api/rendering/BUILD.bazel | 6 +- platform/icons-api/rendering/build.gradle.kts | 1 + .../intellij.platform.icons.api.rendering.iml | 3 +- .../icons-api/rendering/lowlevel/BUILD.bazel | 32 ------ .../rendering/lowlevel/build.gradle.kts | 24 ----- ....platform.icons.api.rendering.lowlevel.iml | 31 ------ .../rendering/lowlevel/module-content.yaml | 3 - ....platform.icons.api.rendering.lowlevel.xml | 9 -- .../icons/rendering/BitmapImageResource.kt | 6 +- .../org/jetbrains/icons/rendering/Bounds.kt | 7 +- .../jetbrains/icons/rendering/IconRenderer.kt | 9 +- .../icons/rendering/IconRendererManager.kt | 8 +- .../icons/rendering/IconUpdateFlow.kt | 5 +- .../icons/rendering/ImageResource.kt | 10 +- .../icons/rendering/ImageResourceProvider.kt | 4 +- .../icons/rendering/LoadingOptions.kt | 4 +- .../jetbrains/icons/rendering/PaintingApi.kt | 10 +- .../icons/rendering/RenderingContext.kt | 4 +- .../rendering/RescalableImageResource.kt | 12 +-- .../lowlevel/GPUImageResourceHolder.kt | 6 +- .../src/org/jetbrains/icons/DeferredIcon.kt | 20 ++++ .../src/org/jetbrains/icons/DynamicIcon.kt | 11 --- .../jetbrains/icons/ExperimentalIconsApi.kt | 26 ----- .../icons-api/src/org/jetbrains/icons/Icon.kt | 3 +- .../src/org/jetbrains/icons/IconManager.kt | 45 +++++++-- .../jetbrains/icons/ImageResourceLocation.kt | 4 +- .../org/jetbrains/icons/InternalIconsApi.kt | 23 ----- .../org/jetbrains/icons/design/BlendMode.kt | 4 +- .../src/org/jetbrains/icons/design/Color.kt | 7 +- .../org/jetbrains/icons/design/IconAlign.kt | 4 +- .../jetbrains/icons/design/IconDesigner.kt | 6 +- .../org/jetbrains/icons/design/IconMargin.kt | 4 +- .../org/jetbrains/icons/design/IconUnits.kt | 12 +-- .../src/org/jetbrains/icons/design/Shape.kt | 8 +- .../icons/design/SvgPatcherDesigner.kt | 7 +- .../jetbrains/icons/filters/ColorFilter.kt | 4 +- .../icons/filters/TintColorFilter.kt | 4 +- .../org/jetbrains/icons/layers/IconLayer.kt | 4 +- .../icons/modifiers/AlignIconModifier.kt | 4 +- .../icons/modifiers/AlphaIconModifier.kt | 4 +- .../icons/modifiers/ColorFilterModifier.kt | 4 +- .../icons/modifiers/CombinedIconModifier.kt | 4 +- .../icons/modifiers/CutoutMarginModifier.kt | 4 +- .../icons/modifiers/HeightIconModifier.kt | 4 +- .../jetbrains/icons/modifiers/IconModifier.kt | 8 +- .../icons/modifiers/MarginIconModifier.kt | 10 +- .../icons/modifiers/StrokeModifier.kt | 6 +- .../icons/modifiers/SvgPatcherModifier.kt | 10 +- .../icons/modifiers/WidthIconModifier.kt | 6 +- .../jetbrains/icons/patchers/SvgPatcher.kt | 10 +- .../org/jetbrains/icons/swing}/SwingIcon.kt | 13 +-- .../jetbrains/icons/swing}/SwingIconLayer.kt | 6 +- platform/icons-impl/BUILD.bazel | 13 ++- platform/icons-impl/build.gradle.kts | 2 +- .../intellij.platform.icons.impl.iml | 4 +- platform/icons-impl/intellij/BUILD.bazel | 10 +- .../intellij.platform.icons.impl.intellij.iml | 4 +- .../icons-impl/intellij/rendering/BUILD.bazel | 99 ------------------- ...platform.icons.impl.intellij.rendering.iml | 59 ----------- ...platform.icons.impl.intellij.rendering.xml | 38 ------- .../impl/intellij/IntelliJIconManager.kt | 8 -- .../impl/intellij/rendering/Convertors.kt | 0 .../intellij/rendering/IconUpdateService.kt | 6 +- .../rendering/ImageResourceLoaderExtension.kt | 0 .../rendering/IntelliJIconRendererManager.kt | 4 +- .../IntelliJMutableIconUpdateFlowImpl.kt | 0 .../rendering/SwingIconLayerRegistration.kt | 2 +- .../rendering/SwingIconLayerRenderer.kt | 2 +- .../custom/CustomIconLayerRendererProvider.kt | 2 +- .../images/DataLoaderImageResourceHolder.kt | 0 .../rendering/images/IntelliJImageResource.kt | 0 .../images/IntelliJImageResourceProvider.kt | 0 .../images/LegacyIconImageResourceHolder.kt | 0 .../images/ModuleImageResourceLoader.kt | 1 - .../images/SwingImageResourceHolder.kt | 0 .../icons/impl/intellij/DynamicIconTest.kt | 2 +- platform/icons-impl/rendering/BUILD.bazel | 47 --------- .../icons-impl/rendering/build.gradle.kts | 30 ------ ...intellij.platform.icons.impl.rendering.iml | 42 -------- ...intellij.platform.icons.impl.rendering.xml | 13 --- .../rendering/DefaultDynamicIconRenderer.kt | 47 --------- .../impl/rendering/DefaultSwingIconManager.kt | 11 --- .../icons/impl/DefaultDeferredIcon.kt | 16 +++ .../icons/impl/DefaultDynamicIcon.kt | 57 ----------- .../icons/impl/DefaultIconManager.kt | 21 ++-- .../icons/impl/layers/ModifierHelpers.kt | 6 +- .../icons/impl/rendering/AwtImageResource.kt | 0 .../rendering/CachedGPUImageResourceHolder.kt | 0 .../CoroutineBasedMutableIconUpdateFlow.kt | 0 .../rendering/DefaultDeferredIconRenderer.kt | 32 ++++++ .../impl/rendering/DefaultIconRenderer.kt | 0 .../rendering/DefaultIconRendererManager.kt | 6 +- .../impl/rendering/DefaultImageModifiers.kt | 0 .../rendering/DefaultImageResourceProvider.kt | 5 +- .../rendering/MutableIconUpdateFlowBase.kt | 0 .../icons/impl/rendering/SwingIcon.kt | 2 + .../icons/impl/rendering/SwingPaintingApi.kt | 3 +- .../layers/AnimatedIconLayerRenderer.kt | 0 .../rendering/layers/DefaultLayerLayout.kt | 0 .../rendering/layers/IconIconLayerRenderer.kt | 2 +- .../impl/rendering/layers/IconLayerManager.kt | 0 .../rendering/layers/IconLayerRenderer.kt | 0 .../layers/ImageIconLayerRenderer.kt | 2 +- .../impl/rendering/layers/LayerHelpers.kt | 0 .../layers/LayoutIconLayerRenderer.kt | 2 +- .../layers/ShapeIconLayerRenderer.kt | 0 .../modifiers/ColorFilterModifier.kt | 0 .../impl/rendering/modifiers/IconModifier.kt | 5 +- .../rendering/modifiers/MarginIconModifier.kt | 0 .../icons/impl/rendering/toAwtColor.kt | 3 +- .../intellij.moduleSets.core.platform.xml | 1 - platform/jewel/ide-laf-bridge/BUILD.bazel | 1 - .../intellij.platform.jewel.ideLafBridge.iml | 1 - .../intellij.platform.jewel.ideLafBridge.xml | 8 +- .../int-ui/int-ui-standalone/BUILD.bazel | 4 - .../int-ui/int-ui-standalone/build.gradle.kts | 1 - ...tellij.platform.jewel.intUi.standalone.iml | 3 +- .../icon/StandaloneIconRendererManager.kt | 3 - .../jewel/samples/showcase/ShowcaseIcons.kt | 18 +--- .../samples/showcase/components/Icons.kt | 10 +- .../samples/standalone/view/TitleBarView.kt | 8 +- platform/jewel/settings.gradle.kts | 8 +- platform/jewel/ui/BUILD.bazel | 7 -- platform/jewel/ui/build.gradle.kts | 2 - .../jewel/ui/intellij.platform.jewel.ui.iml | 4 +- .../ui/icon/ComposeImageResourceProvider.kt | 5 - .../org/jetbrains/jewel/ui/icon/IconKey.kt | 4 +- ...Loader.kt => PathImageResourceLocation.kt} | 0 .../resources/intellij.platform.jewel.ui.xml | 8 +- platform/platform-impl/bootstrap/BUILD.bazel | 2 - .../intellij.platform.ide.bootstrap.iml | 2 - .../com/intellij/platform/ide/bootstrap/ui.kt | 6 -- .../META-INF/intellij.moduleSets.core.ide.xml | 1 - .../intellij.moduleSets.core.lang.xml | 2 - .../intellij.moduleSets.essential.minimal.xml | 2 - .../intellij.moduleSets.essential.xml | 2 - .../intellij.moduleSets.ide.common.xml | 2 - platform/util/BUILD.bazel | 2 - platform/util/intellij.platform.util.iml | 1 - .../com/intellij/ui/icons/CachedImageIcon.kt | 3 +- .../src/com/intellij/ui/icons/LegacyIcon.kt | 35 ------- .../intellij.devkit.compose/BUILD.bazel | 1 - .../intellij.devkit.compose.iml | 1 - .../showcase/ComposePerformanceDemoAction.kt | 4 +- .../src/showcase/Icons.kt | 45 ++++----- 158 files changed, 340 insertions(+), 1144 deletions(-) delete mode 100644 platform/icons-api/legacy-icon-support/BUILD.bazel delete mode 100644 platform/icons-api/legacy-icon-support/README.md delete mode 100644 platform/icons-api/legacy-icon-support/build.gradle.kts delete mode 100644 platform/icons-api/legacy-icon-support/intellij.platform.icons.api.legacyIconSupport.iml delete mode 100644 platform/icons-api/legacy-icon-support/module-content.yaml delete mode 100644 platform/icons-api/legacy-icon-support/resources/intellij.platform.icons.api.legacyIconSupport.xml delete mode 100644 platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIconManager.kt delete mode 100644 platform/icons-api/rendering/lowlevel/BUILD.bazel delete mode 100644 platform/icons-api/rendering/lowlevel/build.gradle.kts delete mode 100644 platform/icons-api/rendering/lowlevel/intellij.platform.icons.api.rendering.lowlevel.iml delete mode 100644 platform/icons-api/rendering/lowlevel/module-content.yaml delete mode 100644 platform/icons-api/rendering/lowlevel/resources/intellij.platform.icons.api.rendering.lowlevel.xml rename platform/icons-api/rendering/{lowlevel => }/src/org/jetbrains/icons/rendering/lowlevel/GPUImageResourceHolder.kt (83%) create mode 100644 platform/icons-api/src/org/jetbrains/icons/DeferredIcon.kt delete mode 100644 platform/icons-api/src/org/jetbrains/icons/DynamicIcon.kt delete mode 100644 platform/icons-api/src/org/jetbrains/icons/ExperimentalIconsApi.kt delete mode 100644 platform/icons-api/src/org/jetbrains/icons/InternalIconsApi.kt rename platform/icons-api/{legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport => src/org/jetbrains/icons/swing}/SwingIcon.kt (78%) rename platform/icons-api/{legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport => src/org/jetbrains/icons/swing}/SwingIconLayer.kt (77%) delete mode 100644 platform/icons-impl/intellij/rendering/BUILD.bazel delete mode 100644 platform/icons-impl/intellij/rendering/intellij.platform.icons.impl.intellij.rendering.iml delete mode 100644 platform/icons-impl/intellij/rendering/resources/intellij.platform.icons.impl.intellij.rendering.xml rename platform/icons-impl/intellij/{rendering => }/src/org/jetbrains/icons/impl/intellij/rendering/Convertors.kt (100%) rename platform/icons-impl/intellij/{rendering => }/src/org/jetbrains/icons/impl/intellij/rendering/IconUpdateService.kt (87%) rename platform/icons-impl/intellij/{rendering => }/src/org/jetbrains/icons/impl/intellij/rendering/ImageResourceLoaderExtension.kt (100%) rename platform/icons-impl/intellij/{rendering => }/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJIconRendererManager.kt (96%) rename platform/icons-impl/intellij/{rendering => }/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJMutableIconUpdateFlowImpl.kt (100%) rename platform/icons-impl/intellij/{rendering => }/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRegistration.kt (89%) rename platform/icons-impl/intellij/{rendering => }/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRenderer.kt (98%) rename platform/icons-impl/intellij/{rendering => }/src/org/jetbrains/icons/impl/intellij/rendering/custom/CustomIconLayerRendererProvider.kt (94%) rename platform/icons-impl/intellij/{rendering => }/src/org/jetbrains/icons/impl/intellij/rendering/images/DataLoaderImageResourceHolder.kt (100%) rename platform/icons-impl/intellij/{rendering => }/src/org/jetbrains/icons/impl/intellij/rendering/images/IntelliJImageResource.kt (100%) rename platform/icons-impl/intellij/{rendering => }/src/org/jetbrains/icons/impl/intellij/rendering/images/IntelliJImageResourceProvider.kt (100%) rename platform/icons-impl/intellij/{rendering => }/src/org/jetbrains/icons/impl/intellij/rendering/images/LegacyIconImageResourceHolder.kt (100%) rename platform/icons-impl/intellij/{rendering => }/src/org/jetbrains/icons/impl/intellij/rendering/images/ModuleImageResourceLoader.kt (97%) rename platform/icons-impl/intellij/{rendering => }/src/org/jetbrains/icons/impl/intellij/rendering/images/SwingImageResourceHolder.kt (100%) delete mode 100644 platform/icons-impl/rendering/BUILD.bazel delete mode 100644 platform/icons-impl/rendering/build.gradle.kts delete mode 100644 platform/icons-impl/rendering/intellij.platform.icons.impl.rendering.iml delete mode 100644 platform/icons-impl/rendering/resources/intellij.platform.icons.impl.rendering.xml delete mode 100644 platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultDynamicIconRenderer.kt delete mode 100644 platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultSwingIconManager.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/DefaultDeferredIcon.kt delete mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/DefaultDynamicIcon.kt rename platform/icons-impl/{rendering => }/src/org/jetbrains/icons/impl/rendering/AwtImageResource.kt (100%) rename platform/icons-impl/{rendering => }/src/org/jetbrains/icons/impl/rendering/CachedGPUImageResourceHolder.kt (100%) rename platform/icons-impl/{rendering => }/src/org/jetbrains/icons/impl/rendering/CoroutineBasedMutableIconUpdateFlow.kt (100%) create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultDeferredIconRenderer.kt rename platform/icons-impl/{rendering => }/src/org/jetbrains/icons/impl/rendering/DefaultIconRenderer.kt (100%) rename platform/icons-impl/{rendering => }/src/org/jetbrains/icons/impl/rendering/DefaultIconRendererManager.kt (90%) rename platform/icons-impl/{rendering => }/src/org/jetbrains/icons/impl/rendering/DefaultImageModifiers.kt (100%) rename platform/icons-impl/{rendering => }/src/org/jetbrains/icons/impl/rendering/DefaultImageResourceProvider.kt (64%) rename platform/icons-impl/{rendering => }/src/org/jetbrains/icons/impl/rendering/MutableIconUpdateFlowBase.kt (100%) rename platform/icons-impl/{rendering => }/src/org/jetbrains/icons/impl/rendering/SwingIcon.kt (96%) rename platform/icons-impl/{rendering => }/src/org/jetbrains/icons/impl/rendering/SwingPaintingApi.kt (98%) rename platform/icons-impl/{rendering => }/src/org/jetbrains/icons/impl/rendering/layers/AnimatedIconLayerRenderer.kt (100%) rename platform/icons-impl/{rendering => }/src/org/jetbrains/icons/impl/rendering/layers/DefaultLayerLayout.kt (100%) rename platform/icons-impl/{rendering => }/src/org/jetbrains/icons/impl/rendering/layers/IconIconLayerRenderer.kt (93%) rename platform/icons-impl/{rendering => }/src/org/jetbrains/icons/impl/rendering/layers/IconLayerManager.kt (100%) rename platform/icons-impl/{rendering => }/src/org/jetbrains/icons/impl/rendering/layers/IconLayerRenderer.kt (100%) rename platform/icons-impl/{rendering => }/src/org/jetbrains/icons/impl/rendering/layers/ImageIconLayerRenderer.kt (95%) rename platform/icons-impl/{rendering => }/src/org/jetbrains/icons/impl/rendering/layers/LayerHelpers.kt (100%) rename platform/icons-impl/{rendering => }/src/org/jetbrains/icons/impl/rendering/layers/LayoutIconLayerRenderer.kt (97%) rename platform/icons-impl/{rendering => }/src/org/jetbrains/icons/impl/rendering/layers/ShapeIconLayerRenderer.kt (100%) rename platform/icons-impl/{rendering => }/src/org/jetbrains/icons/impl/rendering/modifiers/ColorFilterModifier.kt (100%) rename platform/icons-impl/{rendering => }/src/org/jetbrains/icons/impl/rendering/modifiers/IconModifier.kt (96%) rename platform/icons-impl/{rendering => }/src/org/jetbrains/icons/impl/rendering/modifiers/MarginIconModifier.kt (100%) rename platform/icons-impl/{rendering => }/src/org/jetbrains/icons/impl/rendering/toAwtColor.kt (79%) rename platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/{ResourceImageIconLoader.kt => PathImageResourceLocation.kt} (100%) delete mode 100644 platform/util/ui/src/com/intellij/ui/icons/LegacyIcon.kt diff --git a/.idea/modules.xml b/.idea/modules.xml index 20131c1ab389d..250f712de2f6e 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -1192,13 +1192,9 @@ - - - - diff --git a/build/bazel-generated-file-list.txt b/build/bazel-generated-file-list.txt index 1ebe254829fc4..f089d6e798500 100644 --- a/build/bazel-generated-file-list.txt +++ b/build/bazel-generated-file-list.txt @@ -621,13 +621,9 @@ platform/foldings platform/forms_rt platform/icons platform/icons-api -platform/icons-api/legacy-icon-support platform/icons-api/rendering -platform/icons-api/rendering/lowlevel platform/icons-impl platform/icons-impl/intellij -platform/icons-impl/intellij/rendering -platform/icons-impl/rendering platform/ide-core platform/ide-core-impl platform/ide-core/plugins diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CoreModuleSets.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CoreModuleSets.kt index 5b454351ffec0..c0661f491cabe 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CoreModuleSets.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CoreModuleSets.kt @@ -272,9 +272,6 @@ object CoreModuleSets { embeddedModule("intellij.platform.icons.api") embeddedModule("intellij.platform.icons.api.rendering") - embeddedModule("intellij.platform.icons.api.rendering.lowlevel") - embeddedModule("intellij.platform.icons.impl") - embeddedModule("intellij.platform.icons.impl.intellij") // Include minimal RPC infrastructure AFTER core platform modules // (kernel depends on platform.core, so core must be available first) @@ -349,9 +346,8 @@ object CoreModuleSets { // IDE implementation (depends on lang.core, so must come after) embeddedModule("intellij.platform.ide.impl") - embeddedModule("intellij.platform.icons.api.legacyIconSupport") - embeddedModule("intellij.platform.icons.impl.rendering") - embeddedModule("intellij.platform.icons.impl.intellij.rendering") + embeddedModule("intellij.platform.icons.impl") + embeddedModule("intellij.platform.icons.impl.intellij") // Additional dependencies specific to lang.impl and ide.impl embeddedModule("intellij.platform.ide.concurrency") diff --git a/platform/icons-api/BUILD.bazel b/platform/icons-api/BUILD.bazel index 18112f6e69043..5741fde9be103 100644 --- a/platform/icons-api/BUILD.bazel +++ b/platform/icons-api/BUILD.bazel @@ -6,11 +6,6 @@ create_kotlinc_options( name = "custom_icons-api", api_version = "2.2", language_version = "2.2", - opt_in = [ - "org.jetbrains.icons.api.ExperimentalIconsApi", - "org.jetbrains.icons.api.InternalIconsApi", - "org.jetbrains.icons.ExperimentalIconsApi", - ], progressive = False, x_x_language = [] ) @@ -32,6 +27,7 @@ jvm_library( "@lib//:kotlin-stdlib", "//libraries/kotlinx/coroutines/core", "//libraries/kotlinx/serialization/core", + "@lib//:jetbrains-annotations", ] ) ### auto-generated section `build intellij.platform.icons.api` end \ No newline at end of file diff --git a/platform/icons-api/build.gradle.kts b/platform/icons-api/build.gradle.kts index 67042e4f39cdf..ad95c34646677 100644 --- a/platform/icons-api/build.gradle.kts +++ b/platform/icons-api/build.gradle.kts @@ -20,6 +20,7 @@ sourceSets { } dependencies { + api("org.jetbrains:annotations:26.0.2") api(libs.kotlinx.serialization.core) api(libs.kotlinx.coroutines.core) } diff --git a/platform/icons-api/intellij.platform.icons.api.iml b/platform/icons-api/intellij.platform.icons.api.iml index 35c78775e5aff..5da8acae12762 100644 --- a/platform/icons-api/intellij.platform.icons.api.iml +++ b/platform/icons-api/intellij.platform.icons.api.iml @@ -4,7 +4,7 @@ - @@ -33,5 +33,6 @@ + \ No newline at end of file diff --git a/platform/icons-api/legacy-icon-support/BUILD.bazel b/platform/icons-api/legacy-icon-support/BUILD.bazel deleted file mode 100644 index bfdd7e0067e59..0000000000000 --- a/platform/icons-api/legacy-icon-support/BUILD.bazel +++ /dev/null @@ -1,34 +0,0 @@ -### auto-generated section `build intellij.platform.icons.api.legacyIconSupport` start -load("//build:compiler-options.bzl", "create_kotlinc_options") -load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") - -create_kotlinc_options( - name = "custom_legacy-icon-support", - jvm_target = "11", - api_version = "2.2", - language_version = "2.2", - progressive = False, - x_x_language = [] -) - -resourcegroup( - name = "legacy-icon-support_resources", - srcs = glob(["resources/**/*"]), - strip_prefix = "resources" -) - -jvm_library( - name = "legacy-icon-support", - module_name = "intellij.platform.icons.api.legacyIconSupport", - visibility = ["//visibility:public"], - srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), - resources = [":legacy-icon-support_resources"], - kotlinc_opts = ":custom_legacy-icon-support", - deps = [ - "@lib//:kotlin-stdlib", - "//libraries/kotlinx/coroutines/core", - "//platform/icons-api", - "//libraries/kotlinx/serialization/core", - ] -) -### auto-generated section `build intellij.platform.icons.api.legacyIconSupport` end \ No newline at end of file diff --git a/platform/icons-api/legacy-icon-support/README.md b/platform/icons-api/legacy-icon-support/README.md deleted file mode 100644 index 70b5368d1488f..0000000000000 --- a/platform/icons-api/legacy-icon-support/README.md +++ /dev/null @@ -1,22 +0,0 @@ -## Using new icons as swing icon -Just use `Icon.toSwingIcon()` to convert the Icon to this format. -This will actually create IconRenderer, meaning it can load images, so it is wise to reuse this and avoid recreating the Icon multiple times. -The image loader can hit cache and the result can be ok, but beware. - -This should ideally be done only in low-level comments that directly interact with swing api. - -## Using Swing icon inside new API -This is done by using `swingIcon` function, which will add new layer containing the swing icon. -There might be some non-implemented integrations related to specific behavior of some icons. -```kotlin -icon { - swingIcon(AllIcons.Actions.AddDirectory) -} -``` - -## Serialization -In order to add serialization support for custom swing icons, you have to register it's serializer. -This depends on the current icon manager implementation, for example in IntelliJ, you use extension points. - -Use com.intellij.customLegacyIconSerializer extension inside intellij.platform.icons.impl.intellij module. -Check Jewel docs for registering standalone serializers if supported. \ No newline at end of file diff --git a/platform/icons-api/legacy-icon-support/build.gradle.kts b/platform/icons-api/legacy-icon-support/build.gradle.kts deleted file mode 100644 index a9a9398a6c82f..0000000000000 --- a/platform/icons-api/legacy-icon-support/build.gradle.kts +++ /dev/null @@ -1,25 +0,0 @@ -// This file is used by Jewel gradle script, check community/platform/jewel - -plugins { - jewel - alias(libs.plugins.kotlinx.serialization) -} - -sourceSets { - main { - kotlin { - setSrcDirs(listOf("src")) - } - } - - test { - kotlin { - setSrcDirs(listOf("test")) - } - } -} - -dependencies { - api(project(":jb-icons-api")) - api(libs.kotlinx.serialization.core) -} diff --git a/platform/icons-api/legacy-icon-support/intellij.platform.icons.api.legacyIconSupport.iml b/platform/icons-api/legacy-icon-support/intellij.platform.icons.api.legacyIconSupport.iml deleted file mode 100644 index 8910d391c6da8..0000000000000 --- a/platform/icons-api/legacy-icon-support/intellij.platform.icons.api.legacyIconSupport.iml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - $KOTLIN_BUNDLED$/lib/kotlinx-serialization-compiler-plugin.jar - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/platform/icons-api/legacy-icon-support/module-content.yaml b/platform/icons-api/legacy-icon-support/module-content.yaml deleted file mode 100644 index fdc907c85c74a..0000000000000 --- a/platform/icons-api/legacy-icon-support/module-content.yaml +++ /dev/null @@ -1,3 +0,0 @@ - - name: dist.all/lib/intellij.platform.icons.api.legacyIconSupport.jar - modules: - - name: intellij.platform.icons.api.legacyIconSupport \ No newline at end of file diff --git a/platform/icons-api/legacy-icon-support/resources/intellij.platform.icons.api.legacyIconSupport.xml b/platform/icons-api/legacy-icon-support/resources/intellij.platform.icons.api.legacyIconSupport.xml deleted file mode 100644 index eb960f7b3edea..0000000000000 --- a/platform/icons-api/legacy-icon-support/resources/intellij.platform.icons.api.legacyIconSupport.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIconManager.kt b/platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIconManager.kt deleted file mode 100644 index 904dadf8a4537..0000000000000 --- a/platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIconManager.kt +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.jetbrains.icons.legacyIconSupport - -import org.jetbrains.icons.ExperimentalIconsApi -import org.jetbrains.icons.Icon -import java.util.ServiceLoader - -@OptIn(ExperimentalIconsApi::class) -interface SwingIconManager { - fun toSwingIcon(icon: Icon): javax.swing.Icon - - companion object { - @Volatile - private var instance: SwingIconManager? = null - - @JvmStatic - fun getInstance(): SwingIconManager = instance ?: loadFromSPI() - - private fun loadFromSPI(): SwingIconManager = - ServiceLoader.load(SwingIconManager::class.java).firstOrNull() - ?: error("IconManager instance is not set and there is no SPI service on classpath.") - - - fun activate(manager: SwingIconManager) { - instance = manager - } - } -} \ No newline at end of file diff --git a/platform/icons-api/rendering/BUILD.bazel b/platform/icons-api/rendering/BUILD.bazel index 24aa4c9127bc7..3c5330fcd97aa 100644 --- a/platform/icons-api/rendering/BUILD.bazel +++ b/platform/icons-api/rendering/BUILD.bazel @@ -6,11 +6,6 @@ create_kotlinc_options( name = "custom_rendering", api_version = "2.2", language_version = "2.2", - opt_in = [ - "org.jetbrains.icons.api.ExperimentalIconsApi", - "org.jetbrains.icons.api.InternalIconsApi", - "org.jetbrains.icons.ExperimentalIconsApi", - ], progressive = False, x_x_language = [] ) @@ -32,6 +27,7 @@ jvm_library( "@lib//:kotlin-stdlib", "//libraries/kotlinx/coroutines/core", "//platform/icons-api", + "@lib//:jetbrains-annotations", ], exports = ["//platform/icons-api"] ) diff --git a/platform/icons-api/rendering/build.gradle.kts b/platform/icons-api/rendering/build.gradle.kts index f94cde35d82ea..60b910a41059b 100644 --- a/platform/icons-api/rendering/build.gradle.kts +++ b/platform/icons-api/rendering/build.gradle.kts @@ -20,6 +20,7 @@ sourceSets { } dependencies { + api("org.jetbrains:annotations:26.0.2") api(project(":jb-icons-api")) api(libs.kotlinx.coroutines.core) } diff --git a/platform/icons-api/rendering/intellij.platform.icons.api.rendering.iml b/platform/icons-api/rendering/intellij.platform.icons.api.rendering.iml index 95c41f0f8f8bc..fcb79ee11077c 100644 --- a/platform/icons-api/rendering/intellij.platform.icons.api.rendering.iml +++ b/platform/icons-api/rendering/intellij.platform.icons.api.rendering.iml @@ -4,7 +4,7 @@ - @@ -27,5 +27,6 @@ + \ No newline at end of file diff --git a/platform/icons-api/rendering/lowlevel/BUILD.bazel b/platform/icons-api/rendering/lowlevel/BUILD.bazel deleted file mode 100644 index 0157cc35beec9..0000000000000 --- a/platform/icons-api/rendering/lowlevel/BUILD.bazel +++ /dev/null @@ -1,32 +0,0 @@ -### auto-generated section `build intellij.platform.icons.api.rendering.lowlevel` start -load("//build:compiler-options.bzl", "create_kotlinc_options") -load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") - -create_kotlinc_options( - name = "custom_lowlevel", - api_version = "2.2", - language_version = "2.2", - progressive = False, - x_x_language = [] -) - -resourcegroup( - name = "lowlevel_resources", - srcs = glob(["resources/**/*"]), - strip_prefix = "resources" -) - -jvm_library( - name = "lowlevel", - module_name = "intellij.platform.icons.api.rendering.lowlevel", - visibility = ["//visibility:public"], - srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), - resources = [":lowlevel_resources"], - kotlinc_opts = ":custom_lowlevel", - deps = [ - "@lib//:kotlin-stdlib", - "//libraries/kotlinx/coroutines/core", - "//platform/icons-api", - ] -) -### auto-generated section `build intellij.platform.icons.api.rendering.lowlevel` end \ No newline at end of file diff --git a/platform/icons-api/rendering/lowlevel/build.gradle.kts b/platform/icons-api/rendering/lowlevel/build.gradle.kts deleted file mode 100644 index 40b56bcdd5701..0000000000000 --- a/platform/icons-api/rendering/lowlevel/build.gradle.kts +++ /dev/null @@ -1,24 +0,0 @@ -// This file is used by Jewel gradle script, check community/platform/jewel - -plugins { - jewel -} - -sourceSets { - main { - kotlin { - setSrcDirs(listOf("src")) - } - } - - test { - kotlin { - setSrcDirs(listOf("test")) - } - } -} - -dependencies { - api(libs.kotlinx.coroutines.core) - api(project(":jb-icons-api")) -} diff --git a/platform/icons-api/rendering/lowlevel/intellij.platform.icons.api.rendering.lowlevel.iml b/platform/icons-api/rendering/lowlevel/intellij.platform.icons.api.rendering.lowlevel.iml deleted file mode 100644 index a4b1433e5a0e9..0000000000000 --- a/platform/icons-api/rendering/lowlevel/intellij.platform.icons.api.rendering.lowlevel.iml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/platform/icons-api/rendering/lowlevel/module-content.yaml b/platform/icons-api/rendering/lowlevel/module-content.yaml deleted file mode 100644 index 7df87dc04fc82..0000000000000 --- a/platform/icons-api/rendering/lowlevel/module-content.yaml +++ /dev/null @@ -1,3 +0,0 @@ -- name: dist.all/lib/intellij.platform.icons.api.rendering.lowlevel.jar - modules: - - name: intellij.platform.icons.api.rendering.lowlevel \ No newline at end of file diff --git a/platform/icons-api/rendering/lowlevel/resources/intellij.platform.icons.api.rendering.lowlevel.xml b/platform/icons-api/rendering/lowlevel/resources/intellij.platform.icons.api.rendering.lowlevel.xml deleted file mode 100644 index 41244161273f1..0000000000000 --- a/platform/icons-api/rendering/lowlevel/resources/intellij.platform.icons.api.rendering.lowlevel.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/BitmapImageResource.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/BitmapImageResource.kt index cee60b53ec323..6b411a74e781d 100644 --- a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/BitmapImageResource.kt +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/BitmapImageResource.kt @@ -1,9 +1,9 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.rendering -import org.jetbrains.icons.InternalIconsApi +import org.jetbrains.annotations.ApiStatus -@InternalIconsApi +@ApiStatus.Internal interface BitmapImageResource : ImageResource { fun getRGBPixels(): IntArray fun readPrefetchedPixel(pixels: IntArray, x: Int, y: Int): Int? @@ -13,7 +13,7 @@ interface BitmapImageResource : ImageResource { override val height: Int } -@InternalIconsApi +@ApiStatus.Internal object EmptyBitmapImageResource : BitmapImageResource { override fun getRGBPixels(): IntArray = intArrayOf() override fun readPrefetchedPixel(pixels: IntArray, x: Int, y: Int): Int = 0 diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/Bounds.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/Bounds.kt index 30b81e20db8bc..165a21f4cb59f 100644 --- a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/Bounds.kt +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/Bounds.kt @@ -1,10 +1,9 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.rendering -import org.jetbrains.icons.ExperimentalIconsApi -import org.jetbrains.icons.InternalIconsApi +import org.jetbrains.annotations.ApiStatus -@InternalIconsApi +@ApiStatus.Internal class Bounds( val x: Int, val y: Int, @@ -56,7 +55,7 @@ class Bounds( } -@ExperimentalIconsApi +@ApiStatus.Experimental open class Dimensions( val width: Int, val height: Int diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/IconRenderer.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/IconRenderer.kt index 96a62396e0e97..c128b7b7d89f8 100644 --- a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/IconRenderer.kt +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/IconRenderer.kt @@ -1,15 +1,14 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.rendering -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.Icon -import org.jetbrains.icons.InternalIconsApi -@ExperimentalIconsApi +@ApiStatus.Experimental interface IconRenderer { val icon: Icon - @InternalIconsApi + @ApiStatus.Internal fun render(api: PaintingApi) - @InternalIconsApi + @ApiStatus.Internal fun calculateExpectedDimensions(scaling: ScalingContext): Dimensions } \ No newline at end of file diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/IconRendererManager.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/IconRendererManager.kt index 6d05ac66fd72e..26b6a9b27efeb 100644 --- a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/IconRendererManager.kt +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/IconRendererManager.kt @@ -2,14 +2,14 @@ package org.jetbrains.icons.rendering import kotlinx.coroutines.CoroutineScope -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.Icon import java.util.ServiceLoader /** * Manager for Icon renderers, use convenience methods instead (like Icon.createRenderer()) */ -@ExperimentalIconsApi +@ApiStatus.Experimental interface IconRendererManager { /** * This method will create renderer for specific icon, keep in mind that this might be an expensive operation. @@ -17,7 +17,7 @@ interface IconRendererManager { * @param context General render context, this can be used to watch for Icon updates, or set defaults for color filters etc. * @param loadingStrategy Dictates how the Icon loading should be performed, like block thread, show placeholder, or render blank area */ - @ExperimentalIconsApi + @ApiStatus.Experimental fun createRenderer(icon: Icon, context: RenderingContext, loadingStrategy: LoadingStrategy = LoadingStrategy.BlockThread): IconRenderer fun createUpdateFlow(scope: CoroutineScope?, updateCallback: (Int) -> Unit): MutableIconUpdateFlow @@ -53,7 +53,7 @@ interface IconRendererManager { * @param context General render context, this can be used to watch for Icon updates, or set defaults for color filters etc. * @param loadingStrategy Dictates how the Icon loading should be performed, like block thread, show placeholder, or render blank area */ -@ExperimentalIconsApi +@ApiStatus.Experimental fun Icon.createRenderer(context: RenderingContext, loadingStrategy: LoadingStrategy = LoadingStrategy.BlockThread): IconRenderer { return IconRendererManager.getInstance().createRenderer(this, context, loadingStrategy) } diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/IconUpdateFlow.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/IconUpdateFlow.kt index aba17bb32d30d..a4789748ae1fc 100644 --- a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/IconUpdateFlow.kt +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/IconUpdateFlow.kt @@ -3,12 +3,11 @@ package org.jetbrains.icons.rendering import kotlinx.coroutines.flow.Flow import org.jetbrains.icons.Icon -import org.jetbrains.icons.InternalIconsApi +import org.jetbrains.annotations.ApiStatus -@InternalIconsApi typealias IconUpdateFlow = Flow -@InternalIconsApi +@ApiStatus.Internal interface MutableIconUpdateFlow: IconUpdateFlow { fun triggerUpdate() fun triggerDelayedUpdate(delay: Long) diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResource.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResource.kt index 0773bee9f6a4d..38990df4bc953 100644 --- a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResource.kt +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResource.kt @@ -1,23 +1,21 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.rendering -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.ImageResourceLocation -import org.jetbrains.icons.InternalIconsApi import org.jetbrains.icons.filters.ColorFilter import org.jetbrains.icons.patchers.SvgPatcher -@OptIn(InternalIconsApi::class) -@ExperimentalIconsApi +@ApiStatus.Experimental fun imageResource(loader: ImageResourceLocation, imageModifiers: ImageModifiers? = null): ImageResource = ImageResourceProvider.getInstance().loadImage(loader, imageModifiers) -@ExperimentalIconsApi +@ApiStatus.Experimental interface ImageModifiers { val colorFilter: ColorFilter? val svgPatcher: SvgPatcher? } -@ExperimentalIconsApi +@ApiStatus.Experimental interface ImageResource { /** * Image width in pixels, if the image is rescalable this should return default size or null if default size is not set. diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResourceProvider.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResourceProvider.kt index 28a1134fd46b4..3d995fb96310f 100644 --- a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResourceProvider.kt +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/ImageResourceProvider.kt @@ -2,10 +2,10 @@ package org.jetbrains.icons.rendering import org.jetbrains.icons.ImageResourceLocation -import org.jetbrains.icons.InternalIconsApi +import org.jetbrains.annotations.ApiStatus import java.util.ServiceLoader -@InternalIconsApi +@ApiStatus.Internal interface ImageResourceProvider { fun loadImage(location: ImageResourceLocation, imageModifiers: ImageModifiers? = null): ImageResource diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/LoadingOptions.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/LoadingOptions.kt index 25e5af428ea93..1a861625ac5d4 100644 --- a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/LoadingOptions.kt +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/LoadingOptions.kt @@ -1,9 +1,9 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.rendering -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus -@ExperimentalIconsApi +@ApiStatus.Experimental sealed interface LoadingStrategy { class RenderBlank( val dimensions: Dimensions diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/PaintingApi.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/PaintingApi.kt index 50fe84a885ba9..0a79783255853 100644 --- a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/PaintingApi.kt +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/PaintingApi.kt @@ -1,8 +1,7 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.rendering -import org.jetbrains.icons.ExperimentalIconsApi -import org.jetbrains.icons.InternalIconsApi +import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.design.Color import org.jetbrains.icons.filters.ColorFilter @@ -10,11 +9,10 @@ import org.jetbrains.icons.filters.ColorFilter * Abstraction of painting API, this is used to define icons or graphics that are customizable * but also reusable between different environments, where graphic api differ. */ -@InternalIconsApi +@ApiStatus.Internal interface PaintingApi { val bounds: Bounds val scaling: ScalingContext - @OptIn(ExperimentalIconsApi::class) fun drawImage( image: ImageResource, x: Int = 0, @@ -34,14 +32,14 @@ interface PaintingApi { fun withCustomContext(bounds: Bounds, overrideColorFilter: ColorFilter? = null): PaintingApi } -@InternalIconsApi +@ApiStatus.Internal enum class DrawMode { Fill, Clear, Stroke } -@InternalIconsApi +@ApiStatus.Internal interface ScalingContext { val display: Float } \ No newline at end of file diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/RenderingContext.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/RenderingContext.kt index 8886f5b3fe16f..25b257e70c2b9 100644 --- a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/RenderingContext.kt +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/RenderingContext.kt @@ -1,12 +1,12 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.rendering -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus /** * Rendering context affects the behavior of the actual renderers and can be used to, for example, pass update callbacks. */ -@ExperimentalIconsApi +@ApiStatus.Experimental class RenderingContext( /** * Update flow notifies the Icon renderer user about the need to force-rerender the Icon if necessary. (for example on animation frame) diff --git a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/RescalableImageResource.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/RescalableImageResource.kt index ee29efc730b54..44dc7cba18445 100644 --- a/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/RescalableImageResource.kt +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/RescalableImageResource.kt @@ -1,22 +1,20 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.rendering -import org.jetbrains.icons.ExperimentalIconsApi -import org.jetbrains.icons.InternalIconsApi +import org.jetbrains.annotations.ApiStatus -@OptIn(ExperimentalIconsApi::class) -@InternalIconsApi +@ApiStatus.Internal interface RescalableImageResource : ImageResource { fun scale(scale: ImageScale): BitmapImageResource fun calculateExpectedDimensions(scale: ImageScale): Bounds } -@InternalIconsApi +@ApiStatus.Internal sealed interface ImageScale { fun calculateScalingFactorByOriginalDimensions(width: Int, height: Int): Float } -@InternalIconsApi +@ApiStatus.Internal class FixedScale(val scale: Float) : ImageScale { override fun calculateScalingFactorByOriginalDimensions(width: Int, height: Int): Float = scale @@ -34,7 +32,7 @@ class FixedScale(val scale: Float) : ImageScale { } } -@InternalIconsApi +@ApiStatus.Internal class FitAreaScale(val width: Int, val height: Int) : ImageScale { override fun calculateScalingFactorByOriginalDimensions(width: Int, height: Int): Float { val wscale = this.width / width.toFloat() diff --git a/platform/icons-api/rendering/lowlevel/src/org/jetbrains/icons/rendering/lowlevel/GPUImageResourceHolder.kt b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/lowlevel/GPUImageResourceHolder.kt similarity index 83% rename from platform/icons-api/rendering/lowlevel/src/org/jetbrains/icons/rendering/lowlevel/GPUImageResourceHolder.kt rename to platform/icons-api/rendering/src/org/jetbrains/icons/rendering/lowlevel/GPUImageResourceHolder.kt index 1a6971020b76a..07ad6e73603f0 100644 --- a/platform/icons-api/rendering/lowlevel/src/org/jetbrains/icons/rendering/lowlevel/GPUImageResourceHolder.kt +++ b/platform/icons-api/rendering/src/org/jetbrains/icons/rendering/lowlevel/GPUImageResourceHolder.kt @@ -1,10 +1,10 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.rendering.lowlevel -import org.jetbrains.icons.InternalIconsApi +import org.jetbrains.annotations.ApiStatus import kotlin.reflect.KClass -@InternalIconsApi +@ApiStatus.Internal interface GPUImageResourceHolder { fun getOrGenerateBitmap(bitmapClass: KClass, generator: () -> TBitmap): TBitmap -} \ No newline at end of file +} diff --git a/platform/icons-api/src/org/jetbrains/icons/DeferredIcon.kt b/platform/icons-api/src/org/jetbrains/icons/DeferredIcon.kt new file mode 100644 index 0000000000000..c7af7b609b558 --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/DeferredIcon.kt @@ -0,0 +1,20 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons + +import org.jetbrains.annotations.ApiStatus + +@ApiStatus.Experimental +interface DeferredIcon: Icon { + val isDone: Boolean + val placeholder: Icon? +} + +@ApiStatus.Experimental +interface AsyncDeferredIcon: DeferredIcon { + suspend fun resolveInPlaceAsync(): Icon +} + +@ApiStatus.Experimental +interface SyncDeferredIcon: DeferredIcon { + fun resolveInPlace(): Icon +} diff --git a/platform/icons-api/src/org/jetbrains/icons/DynamicIcon.kt b/platform/icons-api/src/org/jetbrains/icons/DynamicIcon.kt deleted file mode 100644 index 6484e70925351..0000000000000 --- a/platform/icons-api/src/org/jetbrains/icons/DynamicIcon.kt +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.jetbrains.icons - -import kotlinx.coroutines.flow.Flow - -@ExperimentalIconsApi -interface DynamicIcon: Icon { - fun getCurrentIcon(): Icon - suspend fun swap(icon: Icon) - fun getFlow(): Flow -} diff --git a/platform/icons-api/src/org/jetbrains/icons/ExperimentalIconsApi.kt b/platform/icons-api/src/org/jetbrains/icons/ExperimentalIconsApi.kt deleted file mode 100644 index 0e82b6d6b0da1..0000000000000 --- a/platform/icons-api/src/org/jetbrains/icons/ExperimentalIconsApi.kt +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.jetbrains.icons - -@RequiresOptIn( - level = RequiresOptIn.Level.WARNING, - message = "This is an experimental API and is likely to change before becoming stable.", -) -@Target( - AnnotationTarget.CLASS, - AnnotationTarget.CONSTRUCTOR, - AnnotationTarget.FIELD, - AnnotationTarget.FUNCTION, - AnnotationTarget.PROPERTY, - AnnotationTarget.PROPERTY_GETTER, - AnnotationTarget.PROPERTY_SETTER, - AnnotationTarget.TYPEALIAS, - AnnotationTarget.VALUE_PARAMETER, -) -/** - * APIs annotated as experimental are subject to change at any time, with no binary nor source compatibility - * guarantees. Behavior might change at any time. - * - * Using any API annotated as experimental in client code should be done with caution, and you will have to take care of - * breakages in your code when usages are impacted by a change. - */ -annotation class ExperimentalIconsApi \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/Icon.kt b/platform/icons-api/src/org/jetbrains/icons/Icon.kt index 82b44683c8cc1..5d15bd7428d6f 100644 --- a/platform/icons-api/src/org/jetbrains/icons/Icon.kt +++ b/platform/icons-api/src/org/jetbrains/icons/Icon.kt @@ -7,6 +7,7 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import org.jetbrains.annotations.ApiStatus /** * This is universal Icon interface that can be used across different environments with different graphics api. @@ -14,5 +15,5 @@ import kotlinx.serialization.encoding.Encoder * * For serialization, use getSerializersModule() on IconManager */ -@ExperimentalIconsApi +@ApiStatus.Experimental interface Icon \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/IconManager.kt b/platform/icons-api/src/org/jetbrains/icons/IconManager.kt index eb2f09616e7d0..8f368576b83e8 100644 --- a/platform/icons-api/src/org/jetbrains/icons/IconManager.kt +++ b/platform/icons-api/src/org/jetbrains/icons/IconManager.kt @@ -1,13 +1,13 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons -import kotlinx.serialization.KSerializer import kotlinx.serialization.modules.SerializersModule +import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.design.IconDesigner import org.jetbrains.icons.modifiers.IconModifier import java.util.ServiceLoader -@ExperimentalIconsApi +@ApiStatus.Experimental interface IconManager { /** * Creates new Icon "description", this is a cheap operation, to render the Icon, use Icon.createRenderer() function. @@ -16,10 +16,20 @@ interface IconManager { fun icon(designer: IconDesigner.() -> Unit): Icon /** - * Dynamic icon allows dynamic icon swapping after the Icon model was created, the renderers created from such - * icon should be able to listen to these changes and swap the actual rendered icon on-the-fly. + * @see org.jetbrains.icons.deferredIcon */ - fun dynamicIcon(icon: Icon): DynamicIcon + fun deferredIcon(placeholder: Icon?, identifier: String? = null, preventClashes: Boolean = true, evaluator: (String) -> Icon?): Icon + + /** + * @see org.jetbrains.icons.deferredIconAsync + */ + fun deferredIconAsync(placeholder: Icon?, identifier: String? = null, preventClashes: Boolean = true, evaluator: suspend (String) -> Icon?): Icon + + /** + * Converts specific Icon to swing Icon. + * ! This is an expensive operation and can include image loading, reuse the instance if possible. ! + */ + fun toSwingIcon(icon: Icon): javax.swing.Icon fun getSerializersModule(): SerializersModule @@ -47,7 +57,7 @@ interface IconManager { * * To render the Icon, renderer has to be obtained first using createRenderer(), however this should be only done * from inside components. (check intellij.platform.icons.api.rendering module), for usage inside swing, - * check intellij.platform.icons.api.legacyIconSupport module. + * use toSwingIcon method. * * Usage: * ''' @@ -58,11 +68,30 @@ interface IconManager { * * Check the designer interface for layer options. Also check intellij.platform.icons.api.legacyIconSupport module * to find out how to convert old icons and new icons. + * + * @see IconManager.toSwingIcon */ fun icon(designer: IconDesigner.() -> Unit): Icon = IconManager.getInstance().icon(designer) -fun dynamicIcon(designer: IconDesigner.() -> Unit): DynamicIcon = IconManager.getInstance().dynamicIcon(icon(designer)) +/** + * Deferred icon allows apis to return an Icon that takes some time to compute; + * optional placeholder can be included to allow rendering it before the actual icon is ready. + * + * To cache such icons and synchronize them over the network, some identifier should be given. + * Implementations might try to prefix the identifier with the source pluginId/moduleId to avoid clashes. + * If the identifier should be global, preventClashes can be set to false to disable this. + * + * If the identifier is not passed, an automatic one is created, however, this will prevent the result + * from being cached, as a new one is generated per each deferredIcon() call. + */ +fun deferredIcon(placeholder: Icon?, identifier: String? = null, preventClashes: Boolean = true, evaluator: (String) -> Icon?): Icon = + IconManager.getInstance().deferredIcon(placeholder, identifier, preventClashes, evaluator) -fun dynamicIcon(icon: Icon): DynamicIcon = IconManager.getInstance().dynamicIcon(icon) +/** + * Alternative for deferredIcon that accepts suspending functions. + * @see deferredIcon + */ +fun deferredIconAsync(placeholder: Icon?, identifier: String? = null, preventClashes: Boolean = true, evaluator: suspend (String) -> Icon?): Icon = + IconManager.getInstance().deferredIconAsync(placeholder, identifier, preventClashes, evaluator) fun imageIcon(path: String, classLoader: ClassLoader? = null, modifier: IconModifier = IconModifier): Icon = icon { image(path, classLoader, modifier) } \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/ImageResourceLocation.kt b/platform/icons-api/src/org/jetbrains/icons/ImageResourceLocation.kt index 2d095a8815805..4ac3d7ed22a29 100644 --- a/platform/icons-api/src/org/jetbrains/icons/ImageResourceLocation.kt +++ b/platform/icons-api/src/org/jetbrains/icons/ImageResourceLocation.kt @@ -1,6 +1,8 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons +import org.jetbrains.annotations.ApiStatus + /** * Represents a place from which image resource can be loaded. * @@ -12,5 +14,5 @@ package org.jetbrains.icons * When creating new locations, ensure to register new ImageResourceLoader (rendering api) for the specific location. * Check current implementation on how to register extensions. */ -@ExperimentalIconsApi +@ApiStatus.Experimental interface ImageResourceLocation \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/InternalIconsApi.kt b/platform/icons-api/src/org/jetbrains/icons/InternalIconsApi.kt deleted file mode 100644 index f5fa9ef1b67d7..0000000000000 --- a/platform/icons-api/src/org/jetbrains/icons/InternalIconsApi.kt +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.jetbrains.icons - -@RequiresOptIn( - level = RequiresOptIn.Level.WARNING, - message = "This is an internal API and is subject to change without notice.", -) -@Target( - AnnotationTarget.CLASS, - AnnotationTarget.CONSTRUCTOR, - AnnotationTarget.FIELD, - AnnotationTarget.FUNCTION, - AnnotationTarget.PROPERTY, - AnnotationTarget.PROPERTY_GETTER, - AnnotationTarget.PROPERTY_SETTER, - AnnotationTarget.TYPEALIAS, - AnnotationTarget.VALUE_PARAMETER, -) -/** - * APIs annotated as internal are not meant for usage in client code; there are no guarantees about the binary - * nor source compatibility, and the behavior can change at any time. **Do not use** these APIs in client code! - */ -annotation class InternalIconsApi \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/design/BlendMode.kt b/platform/icons-api/src/org/jetbrains/icons/design/BlendMode.kt index 5418ea3b801c2..e545e500a0c2b 100644 --- a/platform/icons-api/src/org/jetbrains/icons/design/BlendMode.kt +++ b/platform/icons-api/src/org/jetbrains/icons/design/BlendMode.kt @@ -2,9 +2,9 @@ package org.jetbrains.icons.design import kotlinx.serialization.Serializable -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus -@ExperimentalIconsApi +@ApiStatus.Experimental @Serializable enum class BlendMode { SrcIn, diff --git a/platform/icons-api/src/org/jetbrains/icons/design/Color.kt b/platform/icons-api/src/org/jetbrains/icons/design/Color.kt index cc71870f78391..c3f03ff2d1e58 100644 --- a/platform/icons-api/src/org/jetbrains/icons/design/Color.kt +++ b/platform/icons-api/src/org/jetbrains/icons/design/Color.kt @@ -2,10 +2,10 @@ package org.jetbrains.icons.design import kotlinx.serialization.Serializable -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus import kotlin.math.roundToInt -@ExperimentalIconsApi +@ApiStatus.Experimental @Serializable sealed interface Color { fun toHex(): String @@ -15,8 +15,7 @@ sealed interface Color { } } - -@ExperimentalIconsApi +@ApiStatus.Experimental @Serializable class RGBA( val red: Float, diff --git a/platform/icons-api/src/org/jetbrains/icons/design/IconAlign.kt b/platform/icons-api/src/org/jetbrains/icons/design/IconAlign.kt index 286d3af4defb7..2031d2aaeed0e 100644 --- a/platform/icons-api/src/org/jetbrains/icons/design/IconAlign.kt +++ b/platform/icons-api/src/org/jetbrains/icons/design/IconAlign.kt @@ -2,11 +2,11 @@ package org.jetbrains.icons.design import kotlinx.serialization.Serializable -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus @Suppress("unused") @Serializable -@ExperimentalIconsApi +@ApiStatus.Experimental class IconAlign( val verticalAlign: IconVerticalAlign, val horizontalAlign: IconHorizontalAlign diff --git a/platform/icons-api/src/org/jetbrains/icons/design/IconDesigner.kt b/platform/icons-api/src/org/jetbrains/icons/design/IconDesigner.kt index 39a6c3129d549..63b959cef14d2 100644 --- a/platform/icons-api/src/org/jetbrains/icons/design/IconDesigner.kt +++ b/platform/icons-api/src/org/jetbrains/icons/design/IconDesigner.kt @@ -1,7 +1,7 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.design -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.Icon import org.jetbrains.icons.ImageResourceLocation import org.jetbrains.icons.layers.IconLayer @@ -15,7 +15,7 @@ import org.jetbrains.icons.modifiers.size * * @param IconModifier Modifications that should be performed on the Layer, like sizing, margin, color filters etc. (order-dependant) */ -@ExperimentalIconsApi +@ApiStatus.Experimental interface IconDesigner { fun image(resourceLoader: ImageResourceLocation, modifier: IconModifier = IconModifier) fun image(path: String, classLoader: ClassLoader? = null, modifier: IconModifier = IconModifier) @@ -43,7 +43,7 @@ fun IconDesigner.badge( shape(color, shape, modifier.size(size).align(align).cutoutMargin(cutout)) } -@ExperimentalIconsApi +@ApiStatus.Experimental interface IconAnimationDesigner { fun frame(duration: Long, builder: IconDesigner.() -> Unit) } \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/design/IconMargin.kt b/platform/icons-api/src/org/jetbrains/icons/design/IconMargin.kt index b2c4c52fde8c9..decbdb2206374 100644 --- a/platform/icons-api/src/org/jetbrains/icons/design/IconMargin.kt +++ b/platform/icons-api/src/org/jetbrains/icons/design/IconMargin.kt @@ -2,10 +2,10 @@ package org.jetbrains.icons.design import kotlinx.serialization.Serializable -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus @Serializable -@ExperimentalIconsApi +@ApiStatus.Experimental class IconMargin( val top: IconUnit, val left: IconUnit, diff --git a/platform/icons-api/src/org/jetbrains/icons/design/IconUnits.kt b/platform/icons-api/src/org/jetbrains/icons/design/IconUnits.kt index 29f19d6082ee6..d75289462deaa 100644 --- a/platform/icons-api/src/org/jetbrains/icons/design/IconUnits.kt +++ b/platform/icons-api/src/org/jetbrains/icons/design/IconUnits.kt @@ -2,7 +2,7 @@ package org.jetbrains.icons.design import kotlinx.serialization.Serializable -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus /** * Samples: @@ -15,7 +15,7 @@ import org.jetbrains.icons.ExperimentalIconsApi * */ @Serializable -@ExperimentalIconsApi +@ApiStatus.Experimental sealed interface IconUnit { companion object { val Zero: IconUnit = 0.dp @@ -24,7 +24,7 @@ sealed interface IconUnit { } @Serializable -@ExperimentalIconsApi +@ApiStatus.Experimental class DisplayPointIconUnit(val value: Double) : IconUnit { override fun equals(other: Any?): Boolean { if (this === other) return true @@ -46,7 +46,7 @@ class DisplayPointIconUnit(val value: Double) : IconUnit { } @Serializable -@ExperimentalIconsApi +@ApiStatus.Experimental class PixelIconUnit(val value: Int) : IconUnit { override fun equals(other: Any?): Boolean { if (this === other) return true @@ -67,7 +67,7 @@ class PixelIconUnit(val value: Int) : IconUnit { } @Serializable -@ExperimentalIconsApi +@ApiStatus.Experimental class PercentIconUnit(val value: Double) : IconUnit { override fun equals(other: Any?): Boolean { if (this === other) return true @@ -88,7 +88,7 @@ class PercentIconUnit(val value: Double) : IconUnit { } @Serializable -@ExperimentalIconsApi +@ApiStatus.Experimental object MaxIconUnit : IconUnit val Int.dp: DisplayPointIconUnit diff --git a/platform/icons-api/src/org/jetbrains/icons/design/Shape.kt b/platform/icons-api/src/org/jetbrains/icons/design/Shape.kt index 8565f5616ab75..3296909b54636 100644 --- a/platform/icons-api/src/org/jetbrains/icons/design/Shape.kt +++ b/platform/icons-api/src/org/jetbrains/icons/design/Shape.kt @@ -2,16 +2,16 @@ package org.jetbrains.icons.design import kotlinx.serialization.Serializable -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus -@ExperimentalIconsApi +@ApiStatus.Experimental @Serializable sealed interface Shape { companion object { } } -@ExperimentalIconsApi +@ApiStatus.Experimental @Serializable object Circle: Shape { override fun toString(): String { @@ -20,7 +20,7 @@ object Circle: Shape { } -@ExperimentalIconsApi +@ApiStatus.Experimental @Serializable object Rectangle: Shape { diff --git a/platform/icons-api/src/org/jetbrains/icons/design/SvgPatcherDesigner.kt b/platform/icons-api/src/org/jetbrains/icons/design/SvgPatcherDesigner.kt index f0e1087b58b6f..1df720ce7ce69 100644 --- a/platform/icons-api/src/org/jetbrains/icons/design/SvgPatcherDesigner.kt +++ b/platform/icons-api/src/org/jetbrains/icons/design/SvgPatcherDesigner.kt @@ -1,13 +1,12 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.design -import org.jetbrains.icons.ExperimentalIconsApi -import org.jetbrains.icons.InternalIconsApi +import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.patchers.SvgPatchOperation import org.jetbrains.icons.patchers.SvgPatcher import org.jetbrains.icons.patchers.SvgPathFilteredOperations -@ExperimentalIconsApi +@ApiStatus.Experimental public class SvgPatcherDesigner { private val operations = mutableListOf() private val filteredOperations = mutableListOf() @@ -51,6 +50,6 @@ public class SvgPatcherDesigner { filteredOperations.add(SvgPathFilteredOperations(path, designer.build().operations)) } - @InternalIconsApi + @ApiStatus.Internal internal fun build(): SvgPatcher = SvgPatcher(operations, filteredOperations) } \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/filters/ColorFilter.kt b/platform/icons-api/src/org/jetbrains/icons/filters/ColorFilter.kt index 337fc16bb030a..652cef6bc0fca 100644 --- a/platform/icons-api/src/org/jetbrains/icons/filters/ColorFilter.kt +++ b/platform/icons-api/src/org/jetbrains/icons/filters/ColorFilter.kt @@ -2,8 +2,8 @@ package org.jetbrains.icons.filters import kotlinx.serialization.Serializable -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus -@ExperimentalIconsApi +@ApiStatus.Experimental @Serializable sealed interface ColorFilter \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/filters/TintColorFilter.kt b/platform/icons-api/src/org/jetbrains/icons/filters/TintColorFilter.kt index ada13b39f3c8a..361d2baed6c27 100644 --- a/platform/icons-api/src/org/jetbrains/icons/filters/TintColorFilter.kt +++ b/platform/icons-api/src/org/jetbrains/icons/filters/TintColorFilter.kt @@ -2,11 +2,11 @@ package org.jetbrains.icons.filters import kotlinx.serialization.Serializable -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.design.BlendMode import org.jetbrains.icons.design.Color -@ExperimentalIconsApi +@ApiStatus.Experimental @Serializable class TintColorFilter( val color: Color, diff --git a/platform/icons-api/src/org/jetbrains/icons/layers/IconLayer.kt b/platform/icons-api/src/org/jetbrains/icons/layers/IconLayer.kt index f495b2a819ce6..851a6af8d36a6 100644 --- a/platform/icons-api/src/org/jetbrains/icons/layers/IconLayer.kt +++ b/platform/icons-api/src/org/jetbrains/icons/layers/IconLayer.kt @@ -1,13 +1,13 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.layers -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.modifiers.IconModifier /** * To serialize IconLayer, serializersModule from IconManager might be used. */ -@ExperimentalIconsApi +@ApiStatus.Experimental interface IconLayer { val modifier: IconModifier } \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/modifiers/AlignIconModifier.kt b/platform/icons-api/src/org/jetbrains/icons/modifiers/AlignIconModifier.kt index 39cca93235ae0..9bd82963650f0 100644 --- a/platform/icons-api/src/org/jetbrains/icons/modifiers/AlignIconModifier.kt +++ b/platform/icons-api/src/org/jetbrains/icons/modifiers/AlignIconModifier.kt @@ -2,11 +2,11 @@ package org.jetbrains.icons.modifiers import kotlinx.serialization.Serializable -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.design.IconAlign @Serializable -@ExperimentalIconsApi +@ApiStatus.Experimental class AlignIconModifier( val align: IconAlign ): IconModifier { diff --git a/platform/icons-api/src/org/jetbrains/icons/modifiers/AlphaIconModifier.kt b/platform/icons-api/src/org/jetbrains/icons/modifiers/AlphaIconModifier.kt index 635462047ec8b..8b33601265a6a 100644 --- a/platform/icons-api/src/org/jetbrains/icons/modifiers/AlphaIconModifier.kt +++ b/platform/icons-api/src/org/jetbrains/icons/modifiers/AlphaIconModifier.kt @@ -2,10 +2,10 @@ package org.jetbrains.icons.modifiers import kotlinx.serialization.Serializable -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus @Serializable -@ExperimentalIconsApi +@ApiStatus.Experimental class AlphaIconModifier( val alpha: Float ): IconModifier { diff --git a/platform/icons-api/src/org/jetbrains/icons/modifiers/ColorFilterModifier.kt b/platform/icons-api/src/org/jetbrains/icons/modifiers/ColorFilterModifier.kt index dea79c0adba76..e7f2efa178de0 100644 --- a/platform/icons-api/src/org/jetbrains/icons/modifiers/ColorFilterModifier.kt +++ b/platform/icons-api/src/org/jetbrains/icons/modifiers/ColorFilterModifier.kt @@ -2,14 +2,14 @@ package org.jetbrains.icons.modifiers import kotlinx.serialization.Serializable -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.design.BlendMode import org.jetbrains.icons.design.Color import org.jetbrains.icons.filters.ColorFilter import org.jetbrains.icons.filters.TintColorFilter @Serializable -@ExperimentalIconsApi +@ApiStatus.Experimental class ColorFilterModifier( val colorFilter: ColorFilter ): IconModifier { diff --git a/platform/icons-api/src/org/jetbrains/icons/modifiers/CombinedIconModifier.kt b/platform/icons-api/src/org/jetbrains/icons/modifiers/CombinedIconModifier.kt index 09ccccc2cdef1..674fd63f5b0f2 100644 --- a/platform/icons-api/src/org/jetbrains/icons/modifiers/CombinedIconModifier.kt +++ b/platform/icons-api/src/org/jetbrains/icons/modifiers/CombinedIconModifier.kt @@ -2,10 +2,10 @@ package org.jetbrains.icons.modifiers import kotlinx.serialization.Serializable -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus @Serializable -@ExperimentalIconsApi +@ApiStatus.Experimental class CombinedIconModifier( val root: IconModifier, val other: IconModifier, diff --git a/platform/icons-api/src/org/jetbrains/icons/modifiers/CutoutMarginModifier.kt b/platform/icons-api/src/org/jetbrains/icons/modifiers/CutoutMarginModifier.kt index d83f6cd0bab50..6dd71c315f69a 100644 --- a/platform/icons-api/src/org/jetbrains/icons/modifiers/CutoutMarginModifier.kt +++ b/platform/icons-api/src/org/jetbrains/icons/modifiers/CutoutMarginModifier.kt @@ -2,11 +2,11 @@ package org.jetbrains.icons.modifiers import kotlinx.serialization.Serializable -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.design.IconUnit @Serializable -@ExperimentalIconsApi +@ApiStatus.Experimental /** * Add cutout margin to the specific layer, which will clear the surrounding area * Currently supported only by shape and image layers, image layer will not consider diff --git a/platform/icons-api/src/org/jetbrains/icons/modifiers/HeightIconModifier.kt b/platform/icons-api/src/org/jetbrains/icons/modifiers/HeightIconModifier.kt index 7998ba53f1d37..9c933a6f7da31 100644 --- a/platform/icons-api/src/org/jetbrains/icons/modifiers/HeightIconModifier.kt +++ b/platform/icons-api/src/org/jetbrains/icons/modifiers/HeightIconModifier.kt @@ -2,12 +2,12 @@ package org.jetbrains.icons.modifiers import kotlinx.serialization.Serializable -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.design.MaxIconUnit import org.jetbrains.icons.design.IconUnit @Serializable -@ExperimentalIconsApi +@ApiStatus.Experimental class HeightIconModifier( val height: IconUnit ): IconModifier { diff --git a/platform/icons-api/src/org/jetbrains/icons/modifiers/IconModifier.kt b/platform/icons-api/src/org/jetbrains/icons/modifiers/IconModifier.kt index 498781061e317..7432ab82cf282 100644 --- a/platform/icons-api/src/org/jetbrains/icons/modifiers/IconModifier.kt +++ b/platform/icons-api/src/org/jetbrains/icons/modifiers/IconModifier.kt @@ -1,10 +1,8 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.modifiers -import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import kotlinx.serialization.serializer -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus /** * Modifications that should be performed on the Layer, like sizing, margin, color filters etc. (order-dependant) @@ -22,7 +20,7 @@ import org.jetbrains.icons.ExperimentalIconsApi * @see WidthIconModifier */ @Serializable -@ExperimentalIconsApi +@ApiStatus.Experimental sealed interface IconModifier { companion object: IconModifier } @@ -32,6 +30,6 @@ sealed interface IconModifier { * * Returns a [IconModifier] representing this modifier followed by [other] in sequence. */ -@ExperimentalIconsApi +@ApiStatus.Experimental infix fun IconModifier.then(other: IconModifier): IconModifier = if (other === IconModifier) this else CombinedIconModifier(this, other) \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/modifiers/MarginIconModifier.kt b/platform/icons-api/src/org/jetbrains/icons/modifiers/MarginIconModifier.kt index 1cee61c1f7446..77bf72121fbe1 100644 --- a/platform/icons-api/src/org/jetbrains/icons/modifiers/MarginIconModifier.kt +++ b/platform/icons-api/src/org/jetbrains/icons/modifiers/MarginIconModifier.kt @@ -2,11 +2,11 @@ package org.jetbrains.icons.modifiers import kotlinx.serialization.Serializable -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.design.IconUnit @Serializable -@ExperimentalIconsApi +@ApiStatus.Experimental class MarginIconModifier( val left: IconUnit, val top: IconUnit, @@ -40,17 +40,17 @@ class MarginIconModifier( } } -@ExperimentalIconsApi +@ApiStatus.Experimental fun IconModifier.margin(left: IconUnit, top: IconUnit, right: IconUnit, bottom: IconUnit): IconModifier { return this then MarginIconModifier(left, top, right, bottom) } -@ExperimentalIconsApi +@ApiStatus.Experimental fun IconModifier.margin(all: IconUnit): IconModifier { return margin(all, all, all, all) } -@ExperimentalIconsApi +@ApiStatus.Experimental fun IconModifier.margin(vertical: IconUnit, horizontal: IconUnit): IconModifier { return margin(horizontal, vertical, horizontal, vertical) } \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/modifiers/StrokeModifier.kt b/platform/icons-api/src/org/jetbrains/icons/modifiers/StrokeModifier.kt index c737a1fcbd0d5..5797d7c6dfb9f 100644 --- a/platform/icons-api/src/org/jetbrains/icons/modifiers/StrokeModifier.kt +++ b/platform/icons-api/src/org/jetbrains/icons/modifiers/StrokeModifier.kt @@ -2,11 +2,11 @@ package org.jetbrains.icons.modifiers import kotlinx.serialization.Serializable -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.design.Color @Serializable -@ExperimentalIconsApi +@ApiStatus.Experimental class StrokeModifier( val color: Color ): IconModifier { @@ -29,7 +29,7 @@ class StrokeModifier( } -@ExperimentalIconsApi +@ApiStatus.Experimental fun IconModifier.stroke(color: Color): IconModifier { return this then StrokeModifier(color) } \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/modifiers/SvgPatcherModifier.kt b/platform/icons-api/src/org/jetbrains/icons/modifiers/SvgPatcherModifier.kt index 12d683eaea6b4..b1d95c649406c 100644 --- a/platform/icons-api/src/org/jetbrains/icons/modifiers/SvgPatcherModifier.kt +++ b/platform/icons-api/src/org/jetbrains/icons/modifiers/SvgPatcherModifier.kt @@ -2,12 +2,12 @@ package org.jetbrains.icons.modifiers import kotlinx.serialization.Serializable -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.design.SvgPatcherDesigner import org.jetbrains.icons.patchers.SvgPatcher @Serializable -@ExperimentalIconsApi +@ApiStatus.Experimental class SvgPatcherModifier( val svgPatcher: SvgPatcher ): IconModifier { @@ -30,15 +30,15 @@ class SvgPatcherModifier( } -@ExperimentalIconsApi +@ApiStatus.Experimental fun IconModifier.patchSvg(svgPatcher: SvgPatcher): IconModifier { return this then SvgPatcherModifier(svgPatcher) } -@ExperimentalIconsApi +@ApiStatus.Experimental fun IconModifier.patchSvg(svgPatcherBuilder: SvgPatcherDesigner.() -> Unit): IconModifier { return this.patchSvg(svgPatcher(svgPatcherBuilder)) } -@ExperimentalIconsApi +@ApiStatus.Experimental fun svgPatcher(svgPatcherBuilder: SvgPatcherDesigner.() -> Unit): SvgPatcher = SvgPatcherDesigner().apply(svgPatcherBuilder).build() \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/modifiers/WidthIconModifier.kt b/platform/icons-api/src/org/jetbrains/icons/modifiers/WidthIconModifier.kt index 884ba8fe5e872..cc158e10843fb 100644 --- a/platform/icons-api/src/org/jetbrains/icons/modifiers/WidthIconModifier.kt +++ b/platform/icons-api/src/org/jetbrains/icons/modifiers/WidthIconModifier.kt @@ -2,11 +2,11 @@ package org.jetbrains.icons.modifiers import kotlinx.serialization.Serializable -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.design.IconUnit @Serializable -@ExperimentalIconsApi +@ApiStatus.Experimental class WidthIconModifier( val width: IconUnit ): IconModifier { @@ -28,7 +28,7 @@ class WidthIconModifier( } } -@ExperimentalIconsApi +@ApiStatus.Experimental fun IconModifier.width(width: IconUnit): IconModifier { return this then WidthIconModifier(width) } \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/patchers/SvgPatcher.kt b/platform/icons-api/src/org/jetbrains/icons/patchers/SvgPatcher.kt index 1ba8bc60e4759..acd3ab56022e5 100644 --- a/platform/icons-api/src/org/jetbrains/icons/patchers/SvgPatcher.kt +++ b/platform/icons-api/src/org/jetbrains/icons/patchers/SvgPatcher.kt @@ -2,9 +2,9 @@ package org.jetbrains.icons.patchers import kotlinx.serialization.Serializable -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus -@ExperimentalIconsApi +@ApiStatus.Experimental @Serializable public class SvgPatcher( val operations: List, @@ -34,7 +34,7 @@ public class SvgPatcher( } -@ExperimentalIconsApi +@ApiStatus.Experimental infix fun SvgPatcher?.combineWith(other: SvgPatcher?): SvgPatcher? { if (this == null && other == null) return null if (this == null) return other!! @@ -42,7 +42,7 @@ infix fun SvgPatcher?.combineWith(other: SvgPatcher?): SvgPatcher? { return SvgPatcher(operations + other.operations, filteredOperations + other.filteredOperations) } -@ExperimentalIconsApi +@ApiStatus.Experimental @Serializable public class SvgPathFilteredOperations( val path: String, @@ -72,7 +72,7 @@ public class SvgPathFilteredOperations( } -@ExperimentalIconsApi +@ApiStatus.Experimental @Serializable public class SvgPatchOperation( val attributeName: String, diff --git a/platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIcon.kt b/platform/icons-api/src/org/jetbrains/icons/swing/SwingIcon.kt similarity index 78% rename from platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIcon.kt rename to platform/icons-api/src/org/jetbrains/icons/swing/SwingIcon.kt index e940c067bd566..3bdaaff10ee16 100644 --- a/platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIcon.kt +++ b/platform/icons-api/src/org/jetbrains/icons/swing/SwingIcon.kt @@ -1,8 +1,9 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.jetbrains.icons.legacyIconSupport +package org.jetbrains.icons.swing -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.Icon +import org.jetbrains.icons.IconManager import org.jetbrains.icons.design.IconDesigner import org.jetbrains.icons.icon import org.jetbrains.icons.modifiers.IconModifier @@ -10,7 +11,7 @@ import org.jetbrains.icons.modifiers.IconModifier /** * Layer for embedding swing icons. */ -@ExperimentalIconsApi +@ApiStatus.Experimental fun IconDesigner.swingIcon(legacyIcon: javax.swing.Icon, modifier: IconModifier = IconModifier) { custom(SwingIconLayer(legacyIcon, modifier)) } @@ -18,7 +19,7 @@ fun IconDesigner.swingIcon(legacyIcon: javax.swing.Icon, modifier: IconModifier /** * Shorthand for creating a new icon from swing icon, uses IconDesigner.swingIcon(). */ -@ExperimentalIconsApi +@ApiStatus.Experimental fun javax.swing.Icon.toNewIcon(modifier: IconModifier = IconModifier): Icon { return icon { swingIcon(this@toNewIcon, modifier) @@ -29,7 +30,7 @@ fun javax.swing.Icon.toNewIcon(modifier: IconModifier = IconModifier): Icon { * Converts specific Icon to swing Icon. * ! This is an expensive operation and can include image loading, reuse the instance if possible. ! */ -@ExperimentalIconsApi +@ApiStatus.Experimental fun Icon.toSwingIcon(): javax.swing.Icon { - return SwingIconManager.getInstance().toSwingIcon(this) + return IconManager.getInstance().toSwingIcon(this) } \ No newline at end of file diff --git a/platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIconLayer.kt b/platform/icons-api/src/org/jetbrains/icons/swing/SwingIconLayer.kt similarity index 77% rename from platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIconLayer.kt rename to platform/icons-api/src/org/jetbrains/icons/swing/SwingIconLayer.kt index 138f612554779..a52253294f04f 100644 --- a/platform/icons-api/legacy-icon-support/src/org/jetbrains/icons/legacyIconSupport/SwingIconLayer.kt +++ b/platform/icons-api/src/org/jetbrains/icons/swing/SwingIconLayer.kt @@ -1,13 +1,13 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.jetbrains.icons.legacyIconSupport +package org.jetbrains.icons.swing import kotlinx.serialization.Serializable -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.layers.IconLayer import org.jetbrains.icons.modifiers.IconModifier import javax.swing.Icon -@ExperimentalIconsApi +@ApiStatus.Experimental @Serializable class SwingIconLayer( val legacyIcon: Icon, diff --git a/platform/icons-impl/BUILD.bazel b/platform/icons-impl/BUILD.bazel index 9a2c70ab2f46d..5eed739c50422 100644 --- a/platform/icons-impl/BUILD.bazel +++ b/platform/icons-impl/BUILD.bazel @@ -6,12 +6,6 @@ create_kotlinc_options( name = "custom_icons-impl", api_version = "2.2", language_version = "2.2", - opt_in = [ - "org.jetbrains.icons.api.InternalIconsApi", - "org.jetbrains.icons.api.ExperimentalIconsApi", - "org.jetbrains.icons.ExperimentalIconsApi", - "org.jetbrains.icons.InternalIconsApi", - ], progressive = False, x_x_language = [] ) @@ -33,8 +27,13 @@ jvm_library( "@lib//:kotlin-stdlib", "//libraries/kotlinx/coroutines/core", "//platform/icons-api", + "//platform/icons-api/rendering", "//libraries/kotlinx/serialization/core", + "@lib//:jetbrains-annotations", ], - exports = ["//platform/icons-api"] + exports = [ + "//platform/icons-api", + "//platform/icons-api/rendering", + ] ) ### auto-generated section `build intellij.platform.icons.impl` end \ No newline at end of file diff --git a/platform/icons-impl/build.gradle.kts b/platform/icons-impl/build.gradle.kts index ac9a9410896a8..b1306f2897fe1 100644 --- a/platform/icons-impl/build.gradle.kts +++ b/platform/icons-impl/build.gradle.kts @@ -20,9 +20,9 @@ sourceSets { } dependencies { + api("org.jetbrains:annotations:26.0.2") api(project(":jb-icons-api")) api(project(":jb-icons-api-rendering")) - api(project(":jb-icons-api-rendering-lowlevel")) api(libs.kotlinx.serialization.core) api(libs.kotlinx.coroutines.core) } diff --git a/platform/icons-impl/intellij.platform.icons.impl.iml b/platform/icons-impl/intellij.platform.icons.impl.iml index 655489b54488a..6e14f9483733e 100644 --- a/platform/icons-impl/intellij.platform.icons.impl.iml +++ b/platform/icons-impl/intellij.platform.icons.impl.iml @@ -4,7 +4,7 @@ - @@ -33,7 +33,9 @@ + + \ No newline at end of file diff --git a/platform/icons-impl/intellij/BUILD.bazel b/platform/icons-impl/intellij/BUILD.bazel index dca6deff2212c..f2b65a9b60416 100644 --- a/platform/icons-impl/intellij/BUILD.bazel +++ b/platform/icons-impl/intellij/BUILD.bazel @@ -6,12 +6,6 @@ create_kotlinc_options( name = "custom_intellij", api_version = "2.2", language_version = "2.2", - opt_in = [ - "org.jetbrains.icons.api.ExperimentalIconsApi", - "org.jetbrains.icons.api.InternalIconsApi", - "org.jetbrains.icons.ExperimentalIconsApi", - "org.jetbrains.icons.InternalIconsApi", - ], progressive = False, x_x_language = [] ) @@ -33,6 +27,7 @@ jvm_library( "@lib//:kotlin-stdlib", "//libraries/kotlinx/coroutines/core", "//platform/icons-api", + "//platform/icons-api/rendering", "//platform/icons-impl", "//platform/util", "//platform/util:util-ui", @@ -43,6 +38,7 @@ jvm_library( "//platform/platform-impl:ide-impl", "//platform/core-ui", "//libraries/kotlinx/serialization/core", + "@lib//:jetbrains-annotations", ] ) @@ -56,6 +52,7 @@ jvm_library( "@lib//:kotlin-stdlib", "//libraries/kotlinx/coroutines/core", "//platform/icons-api", + "//platform/icons-api/rendering", "//platform/icons-impl", "//platform/util", "//platform/util:util-ui", @@ -73,6 +70,7 @@ jvm_library( "@lib//:junit5Jupiter", "@lib//:assert_j", "//libraries/kotlinx/serialization/core", + "@lib//:jetbrains-annotations", ] ) ### auto-generated section `build intellij.platform.icons.impl.intellij` end diff --git a/platform/icons-impl/intellij/intellij.platform.icons.impl.intellij.iml b/platform/icons-impl/intellij/intellij.platform.icons.impl.intellij.iml index 3cd17717a658f..ad55ff1d0e3d0 100644 --- a/platform/icons-impl/intellij/intellij.platform.icons.impl.intellij.iml +++ b/platform/icons-impl/intellij/intellij.platform.icons.impl.intellij.iml @@ -4,7 +4,7 @@ - @@ -34,6 +34,7 @@ + @@ -50,5 +51,6 @@ + \ No newline at end of file diff --git a/platform/icons-impl/intellij/rendering/BUILD.bazel b/platform/icons-impl/intellij/rendering/BUILD.bazel deleted file mode 100644 index 2b96aa2f9848f..0000000000000 --- a/platform/icons-impl/intellij/rendering/BUILD.bazel +++ /dev/null @@ -1,99 +0,0 @@ -### auto-generated section `build intellij.platform.icons.impl.intellij.rendering` start -load("//build:compiler-options.bzl", "create_kotlinc_options") -load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") - -create_kotlinc_options( - name = "custom_rendering", - api_version = "2.2", - language_version = "2.2", - opt_in = [ - "org.jetbrains.icons.api.ExperimentalIconsApi", - "org.jetbrains.icons.api.InternalIconsApi", - "org.jetbrains.icons.ExperimentalIconsApi", - "org.jetbrains.icons.InternalIconsApi", - ], - progressive = False, - x_x_language = [] -) - -resourcegroup( - name = "rendering_resources", - srcs = glob(["resources/**/*"]), - strip_prefix = "resources" -) - -jvm_library( - name = "rendering", - module_name = "intellij.platform.icons.impl.intellij.rendering", - visibility = ["//visibility:public"], - srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), - resources = [":rendering_resources"], - kotlinc_opts = ":custom_rendering", - deps = [ - "@lib//:kotlin-stdlib", - "//libraries/kotlinx/coroutines/core", - "//platform/icons-api", - "//platform/icons-api/rendering", - "//platform/icons-impl", - "//platform/icons-impl/rendering", - "//platform/icons-impl/intellij", - "//platform/util", - "//platform/util:util-ui", - "//platform/core-api:core", - "//platform/platform-impl/rpc", - "//fleet/util/core", - "//platform/core-impl", - "//platform/icons-api/legacy-icon-support", - "//platform/platform-impl:ide-impl", - "//platform/core-ui", - "//libraries/kotlinx/serialization/core", - "//platform/service-container", - ] -) - -jvm_library( - name = "rendering_test_lib", - visibility = ["//visibility:public"], - srcs = glob(["test/**/*.kt", "test/**/*.java", "test/**/*.form"], allow_empty = True), - kotlinc_opts = ":custom_rendering", - associates = [":rendering"], - deps = [ - "@lib//:kotlin-stdlib", - "//libraries/kotlinx/coroutines/core", - "//platform/icons-api", - "//platform/icons-api/rendering", - "//platform/icons-impl", - "//platform/icons-impl/rendering", - "//platform/icons-impl/intellij", - "//platform/icons-impl/intellij:intellij_test_lib", - "//platform/util", - "//platform/util:util-ui", - "//platform/core-api:core", - "//platform/platform-impl/rpc", - "//fleet/util/core", - "//platform/core-impl", - "//libraries/junit5", - "//platform/testFramework/junit5", - "//platform/testFramework/junit5:junit5_test_lib", - "//platform/icons-api/legacy-icon-support", - "//platform/platform-impl:ide-impl", - "//platform/core-ui", - "//libraries/kotlinx/serialization/json", - "@lib//:junit5", - "@lib//:junit5Jupiter", - "@lib//:assert_j", - "//libraries/kotlinx/serialization/core", - "//platform/service-container", - "//platform/service-container:service-container_test_lib", - ] -) -### auto-generated section `build intellij.platform.icons.impl.intellij.rendering` end - -### auto-generated section `test intellij.platform.icons.impl.intellij.rendering` start -load("@community//build:tests-options.bzl", "jps_test") - -jps_test( - name = "rendering_test", - runtime_deps = [":rendering_test_lib"] -) -### auto-generated section `test intellij.platform.icons.impl.intellij.rendering` end \ No newline at end of file diff --git a/platform/icons-impl/intellij/rendering/intellij.platform.icons.impl.intellij.rendering.iml b/platform/icons-impl/intellij/rendering/intellij.platform.icons.impl.intellij.rendering.iml deleted file mode 100644 index 7c69c5ae1241a..0000000000000 --- a/platform/icons-impl/intellij/rendering/intellij.platform.icons.impl.intellij.rendering.iml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - $KOTLIN_BUNDLED$/lib/kotlinx-serialization-compiler-plugin.jar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/platform/icons-impl/intellij/rendering/resources/intellij.platform.icons.impl.intellij.rendering.xml b/platform/icons-impl/intellij/rendering/resources/intellij.platform.icons.impl.intellij.rendering.xml deleted file mode 100644 index 64c318c60fda7..0000000000000 --- a/platform/icons-impl/intellij/rendering/resources/intellij.platform.icons.impl.intellij.rendering.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt index c35ca4feb1d00..b86199bf43504 100644 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt @@ -2,7 +2,6 @@ package org.jetbrains.icons.impl.intellij import kotlinx.serialization.modules.SerializersModuleBuilder -import org.jetbrains.icons.DynamicIcon import org.jetbrains.icons.Icon import org.jetbrains.icons.ImageResourceLocation import org.jetbrains.icons.design.IconDesigner @@ -18,13 +17,6 @@ class IntelliJIconManager : DefaultIconManager() { return ijIconDesigner.build() } - override fun dynamicIcon(icon: Icon): DynamicIcon { - return createDynamicIcon(icon) { _, _ -> - // TODO Send updates over network and across serialization (also listen for them) - // When implemeted, also uncomment DynamicIconTest - } - } - override fun SerializersModuleBuilder.buildCustomSerializers() { CustomLegacyIconSerializer.registerSerializersTo(this) CustomIconLayerRegistration.registerSerializersTo(this) diff --git a/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/Convertors.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/Convertors.kt similarity index 100% rename from platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/Convertors.kt rename to platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/Convertors.kt diff --git a/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/IconUpdateService.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IconUpdateService.kt similarity index 87% rename from platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/IconUpdateService.kt rename to platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IconUpdateService.kt index 751b883b86750..c7e74247ac8b5 100644 --- a/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/IconUpdateService.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IconUpdateService.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.impl.intellij.rendering import com.intellij.openapi.components.Service @@ -8,10 +8,10 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch import org.jetbrains.annotations.ApiStatus -import org.jetbrains.icons.InternalIconsApi +import org.jetbrains.annotations.ApiStatus @Service(Service.Level.APP) -@InternalIconsApi +@ApiStatus.Internal class IconUpdateService(val scope: CoroutineScope) { @ApiStatus.Experimental fun scheduleDelayedUpdate(delay: Long, updateId: Int, flow: MutableSharedFlow, updateCallback: (Int) -> Unit, rateLimiter: () -> Boolean = { false }) { diff --git a/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/ImageResourceLoaderExtension.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/ImageResourceLoaderExtension.kt similarity index 100% rename from platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/ImageResourceLoaderExtension.kt rename to platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/ImageResourceLoaderExtension.kt diff --git a/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJIconRendererManager.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJIconRendererManager.kt similarity index 96% rename from platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJIconRendererManager.kt rename to platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJIconRendererManager.kt index 9123322f2b699..0f85af9ce6ad0 100644 --- a/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJIconRendererManager.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJIconRendererManager.kt @@ -3,8 +3,8 @@ package org.jetbrains.icons.impl.intellij.rendering import com.intellij.util.ui.StartupUiUtil import kotlinx.coroutines.CoroutineScope -import org.jetbrains.icons.ExperimentalIconsApi -import org.jetbrains.icons.InternalIconsApi +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.impl.intellij.rendering.custom.CustomIconLayerRendererProvider import org.jetbrains.icons.rendering.ImageModifiers import org.jetbrains.icons.rendering.MutableIconUpdateFlow diff --git a/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJMutableIconUpdateFlowImpl.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJMutableIconUpdateFlowImpl.kt similarity index 100% rename from platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJMutableIconUpdateFlowImpl.kt rename to platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJMutableIconUpdateFlowImpl.kt diff --git a/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRegistration.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRegistration.kt similarity index 89% rename from platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRegistration.kt rename to platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRegistration.kt index 0568cdd87987f..96282a9995edd 100644 --- a/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRegistration.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRegistration.kt @@ -3,7 +3,7 @@ package org.jetbrains.icons.impl.intellij.rendering import kotlinx.serialization.KSerializer import org.jetbrains.icons.impl.intellij.custom.CustomIconLayerRegistration -import org.jetbrains.icons.legacyIconSupport.SwingIconLayer +import org.jetbrains.icons.swing.SwingIconLayer internal class SwingIconLayerRegistration: CustomIconLayerRegistration(SwingIconLayer::class) { override fun createSerializer(): KSerializer { diff --git a/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRenderer.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRenderer.kt similarity index 98% rename from platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRenderer.kt rename to platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRenderer.kt index 6f48b54e9e664..6adf29c48caa6 100644 --- a/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRenderer.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRenderer.kt @@ -18,7 +18,7 @@ import org.jetbrains.icons.rendering.ScalingContext import org.jetbrains.icons.impl.rendering.layers.applyTo import org.jetbrains.icons.impl.rendering.layers.generateImageModifiers import org.jetbrains.icons.layers.IconLayer -import org.jetbrains.icons.legacyIconSupport.SwingIconLayer +import org.jetbrains.icons.swing.SwingIconLayer import javax.swing.Icon class SwingIconLayerRendererProvider: CustomIconLayerRendererProvider { diff --git a/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/custom/CustomIconLayerRendererProvider.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/custom/CustomIconLayerRendererProvider.kt similarity index 94% rename from platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/custom/CustomIconLayerRendererProvider.kt rename to platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/custom/CustomIconLayerRendererProvider.kt index eb21db7f41819..f5642ce7e1519 100644 --- a/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/custom/CustomIconLayerRendererProvider.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/custom/CustomIconLayerRendererProvider.kt @@ -5,7 +5,7 @@ import com.intellij.openapi.extensions.ExtensionPointName import org.jetbrains.icons.impl.intellij.rendering.SwingIconLayerRenderer import org.jetbrains.icons.impl.rendering.layers.IconLayerRenderer import org.jetbrains.icons.layers.IconLayer -import org.jetbrains.icons.legacyIconSupport.SwingIconLayer +import org.jetbrains.icons.swing.SwingIconLayer import org.jetbrains.icons.rendering.RenderingContext interface CustomIconLayerRendererProvider { diff --git a/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/DataLoaderImageResourceHolder.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/images/DataLoaderImageResourceHolder.kt similarity index 100% rename from platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/DataLoaderImageResourceHolder.kt rename to platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/images/DataLoaderImageResourceHolder.kt diff --git a/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/IntelliJImageResource.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/images/IntelliJImageResource.kt similarity index 100% rename from platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/IntelliJImageResource.kt rename to platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/images/IntelliJImageResource.kt diff --git a/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/IntelliJImageResourceProvider.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/images/IntelliJImageResourceProvider.kt similarity index 100% rename from platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/IntelliJImageResourceProvider.kt rename to platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/images/IntelliJImageResourceProvider.kt diff --git a/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/LegacyIconImageResourceHolder.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/images/LegacyIconImageResourceHolder.kt similarity index 100% rename from platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/LegacyIconImageResourceHolder.kt rename to platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/images/LegacyIconImageResourceHolder.kt diff --git a/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/ModuleImageResourceLoader.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/images/ModuleImageResourceLoader.kt similarity index 97% rename from platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/ModuleImageResourceLoader.kt rename to platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/images/ModuleImageResourceLoader.kt index 9bf0d22ccfac9..e9305e485168b 100644 --- a/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/ModuleImageResourceLoader.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/images/ModuleImageResourceLoader.kt @@ -7,7 +7,6 @@ import com.intellij.openapi.extensions.PluginId import com.intellij.openapi.util.IntellijInternalApi import com.intellij.ui.icons.findIconLoaderByPath import org.jetbrains.icons.impl.intellij.ModuleImageResourceLocation -import org.jetbrains.icons.rendering.Dimensions import org.jetbrains.icons.rendering.ImageModifiers import org.jetbrains.icons.rendering.ImageResource import org.jetbrains.icons.rendering.ImageResourceLoader diff --git a/platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/SwingImageResourceHolder.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/images/SwingImageResourceHolder.kt similarity index 100% rename from platform/icons-impl/intellij/rendering/src/org/jetbrains/icons/impl/intellij/rendering/images/SwingImageResourceHolder.kt rename to platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/images/SwingImageResourceHolder.kt diff --git a/platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DynamicIconTest.kt b/platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DynamicIconTest.kt index 237e2660f5540..326b68c9c85a5 100644 --- a/platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DynamicIconTest.kt +++ b/platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DynamicIconTest.kt @@ -14,7 +14,7 @@ import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.Json import org.assertj.core.api.Assertions.assertThat import org.jetbrains.icons.DynamicIcon -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.Icon import org.jetbrains.icons.IconManager import org.jetbrains.icons.dynamicIcon diff --git a/platform/icons-impl/rendering/BUILD.bazel b/platform/icons-impl/rendering/BUILD.bazel deleted file mode 100644 index 5a2bd3194ff04..0000000000000 --- a/platform/icons-impl/rendering/BUILD.bazel +++ /dev/null @@ -1,47 +0,0 @@ -### auto-generated section `build intellij.platform.icons.impl.rendering` start -load("//build:compiler-options.bzl", "create_kotlinc_options") -load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") - -create_kotlinc_options( - name = "custom_rendering", - api_version = "2.2", - language_version = "2.2", - opt_in = [ - "org.jetbrains.icons.api.InternalIconsApi", - "org.jetbrains.icons.api.ExperimentalIconsApi", - "org.jetbrains.icons.ExperimentalIconsApi", - "org.jetbrains.icons.InternalIconsApi", - ], - progressive = False, - x_x_language = [] -) - -resourcegroup( - name = "rendering_resources", - srcs = glob(["resources/**/*"]), - strip_prefix = "resources" -) - -jvm_library( - name = "rendering", - module_name = "intellij.platform.icons.impl.rendering", - visibility = ["//visibility:public"], - srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), - resources = [":rendering_resources"], - kotlinc_opts = ":custom_rendering", - deps = [ - "@lib//:kotlin-stdlib", - "//libraries/kotlinx/coroutines/core", - "//platform/icons-impl", - "//platform/icons-api/rendering", - "//platform/icons-api/rendering/lowlevel", - "//libraries/kotlinx/serialization/core", - "//platform/icons-api/legacy-icon-support", - ], - exports = [ - "//platform/icons-impl", - "//platform/icons-api/rendering", - "//platform/icons-api/rendering/lowlevel", - ] -) -### auto-generated section `build intellij.platform.icons.impl.rendering` end \ No newline at end of file diff --git a/platform/icons-impl/rendering/build.gradle.kts b/platform/icons-impl/rendering/build.gradle.kts deleted file mode 100644 index d4a80ad56002f..0000000000000 --- a/platform/icons-impl/rendering/build.gradle.kts +++ /dev/null @@ -1,30 +0,0 @@ -// This file is used by Jewel gradle script, check community/platform/jewel - -plugins { - jewel - alias(libs.plugins.kotlinx.serialization) -} - -sourceSets { - main { - kotlin { - setSrcDirs(listOf("src")) - } - } - - test { - kotlin { - setSrcDirs(listOf("test")) - } - } -} - -dependencies { - api(project(":jb-icons-api")) - api(project(":jb-icons-api-rendering")) - api(project(":jb-icons-api-rendering-lowlevel")) - api(project(":jb-icons-impl")) - api(project(":jb-icons-legacy-icon-support")) - api(libs.kotlinx.serialization.core) - api(libs.kotlinx.coroutines.core) -} diff --git a/platform/icons-impl/rendering/intellij.platform.icons.impl.rendering.iml b/platform/icons-impl/rendering/intellij.platform.icons.impl.rendering.iml deleted file mode 100644 index c40f038f26aad..0000000000000 --- a/platform/icons-impl/rendering/intellij.platform.icons.impl.rendering.iml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - $KOTLIN_BUNDLED$/lib/kotlinx-serialization-compiler-plugin.jar - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/platform/icons-impl/rendering/resources/intellij.platform.icons.impl.rendering.xml b/platform/icons-impl/rendering/resources/intellij.platform.icons.impl.rendering.xml deleted file mode 100644 index 6b7a378ea48be..0000000000000 --- a/platform/icons-impl/rendering/resources/intellij.platform.icons.impl.rendering.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultDynamicIconRenderer.kt b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultDynamicIconRenderer.kt deleted file mode 100644 index 5cd9597efc4d8..0000000000000 --- a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultDynamicIconRenderer.kt +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.jetbrains.icons.impl.rendering - -import org.jetbrains.icons.DynamicIcon -import org.jetbrains.icons.Icon -import org.jetbrains.icons.InternalIconsApi -import org.jetbrains.icons.rendering.Dimensions -import org.jetbrains.icons.rendering.IconRenderer -import org.jetbrains.icons.rendering.LoadingStrategy -import org.jetbrains.icons.rendering.PaintingApi -import org.jetbrains.icons.rendering.RenderingContext -import org.jetbrains.icons.rendering.ScalingContext -import org.jetbrains.icons.rendering.createRenderer -import java.lang.ref.WeakReference - -internal class DefaultDynamicIconRenderer( - override val icon: DynamicIcon, - val renderingContext: RenderingContext, - loadingStrategy: LoadingStrategy -): IconRenderer { - private var currentIcon = icon.getCurrentIcon() - private var renderer = currentIcon.createRenderer(renderingContext, loadingStrategy) - - init { - val ref = WeakReference(this) - renderingContext.updateFlow.collectDynamic(icon.getFlow()) { - ref.get()?.swapIcons(it) - } - } - - private fun swapIcons(newIcon: Icon) { - if (currentIcon === newIcon) return - currentIcon = newIcon - renderer = newIcon.createRenderer(renderingContext, LoadingStrategy.RenderPlaceholder(renderer)) - renderingContext.updateFlow.triggerUpdate() - } - - @InternalIconsApi - override fun render(api: PaintingApi) { - renderer.render(api) - } - - @InternalIconsApi - override fun calculateExpectedDimensions(scaling: ScalingContext): Dimensions { - return renderer.calculateExpectedDimensions(scaling) - } -} diff --git a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultSwingIconManager.kt b/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultSwingIconManager.kt deleted file mode 100644 index a245d064037bd..0000000000000 --- a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultSwingIconManager.kt +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.jetbrains.icons.impl.rendering - -import org.jetbrains.icons.legacyIconSupport.SwingIconManager -import javax.swing.Icon - -class DefaultSwingIconManager: SwingIconManager { - override fun toSwingIcon(icon: org.jetbrains.icons.Icon): Icon { - return SwingIcon(icon) - } -} diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultDeferredIcon.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultDeferredIcon.kt new file mode 100644 index 0000000000000..9541576637436 --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultDeferredIcon.kt @@ -0,0 +1,16 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.DeferredIcon +import org.jetbrains.icons.Icon + +@Serializable +open class DefaultDeferredIcon( + internal val id: String?, + override val placeholder: Icon?, +): DeferredIcon { + override val isDone: Boolean = false + internal val currentIcon: Icon? = placeholder + +} diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultDynamicIcon.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultDynamicIcon.kt deleted file mode 100644 index 2297db2f69a22..0000000000000 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultDynamicIcon.kt +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.jetbrains.icons.impl - -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.serialization.Serializable -import org.jetbrains.icons.DynamicIcon -import org.jetbrains.icons.Icon - -@Serializable -open class DefaultDynamicIcon( - internal val id: String, - internal var currentIcon: DefaultLayeredIcon, - @kotlinx.serialization.Transient - private val updateListener: ((DynamicIcon, Icon) -> Unit)? = null -): DynamicIcon { - @kotlinx.serialization.Transient - private val updateFlow = MutableSharedFlow() - - override fun getCurrentIcon(): Icon { - return currentIcon - } - - override suspend fun swap(icon: Icon) { - if (icon !is DefaultLayeredIcon) error("Unsupported icon type: $icon") - currentIcon = icon - updateListener?.invoke(this, icon) - updateFlow.emit(icon) - } - - override fun getFlow(): Flow { - return updateFlow - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as DefaultDynamicIcon - - if (id != other.id) return false - if (currentIcon != other.currentIcon) return false - - return true - } - - override fun hashCode(): Int { - var result = id.hashCode() - result = 31 * result + currentIcon.hashCode() - return result - } - - override fun toString(): String { - return "IntelliJDynamicIcon(id='$id', currentIcon=$currentIcon)" - } - -} diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultIconManager.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultIconManager.kt index dfcde427635f9..0cafe56e8d525 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultIconManager.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultIconManager.kt @@ -2,8 +2,6 @@ package org.jetbrains.icons.impl import kotlinx.serialization.KSerializer -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.buildClassSerialDescriptor import kotlinx.serialization.descriptors.serialDescriptor @@ -14,24 +12,23 @@ import kotlinx.serialization.encoding.decodeStructure import kotlinx.serialization.encoding.encodeStructure import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.SerializersModuleBuilder -import org.jetbrains.icons.DynamicIcon +import org.jetbrains.icons.DeferredIcon import org.jetbrains.icons.Icon import org.jetbrains.icons.IconManager import org.jetbrains.icons.impl.layers.AnimatedIconLayer import org.jetbrains.icons.impl.layers.IconIconLayer -import org.jetbrains.icons.layers.IconLayer import org.jetbrains.icons.impl.layers.ImageIconLayer import org.jetbrains.icons.impl.layers.LayoutIconLayer import org.jetbrains.icons.impl.layers.ShapeIconLayer import org.jetbrains.icons.modifiers.IconModifier abstract class DefaultIconManager: IconManager { - override fun dynamicIcon(icon: Icon): DynamicIcon { - return DefaultDynamicIcon("dynamic_" + dynamicIconNextId++, icon as DefaultLayeredIcon) + override fun deferredIcon(placeholder: Icon?, identifier: String?, preventClashes: Boolean, evaluator: (String) -> Icon?): Icon { + return DefaultDeferredIcon(identifier, placeholder) } - protected fun createDynamicIcon(icon: Icon, updateListener: (DynamicIcon, Icon) -> Unit): DynamicIcon { - return DefaultDynamicIcon("dynamic?" + dynamicIconNextId++, icon as DefaultLayeredIcon, updateListener) + override fun deferredIconAsync(placeholder: Icon?, identifier: String?, preventClashes: Boolean, evaluator: suspend (String) -> Icon?): Icon { + return DefaultDeferredIcon(identifier, placeholder) } protected open fun SerializersModuleBuilder.buildCustomSerializers() { @@ -41,8 +38,8 @@ abstract class DefaultIconManager: IconManager { override fun getSerializersModule(): SerializersModule { return SerializersModule { polymorphic(Icon::class, DefaultLayeredIcon::class, DefaultLayeredIcon.serializer()) - polymorphic(Icon::class, DefaultDynamicIcon::class, DefaultDynamicIcon.serializer()) - polymorphic(DynamicIcon::class, DefaultDynamicIcon::class, DefaultDynamicIcon.serializer()) + polymorphic(Icon::class, DefaultDeferredIcon::class, DefaultDeferredIcon.serializer()) + polymorphic(DeferredIcon::class, DefaultDeferredIcon::class, DefaultDeferredIcon.serializer()) polymorphic( IconModifier::class, IconModifier.Companion::class, @@ -59,6 +56,10 @@ abstract class DefaultIconManager: IconManager { } } + override fun toSwingIcon(icon: Icon): javax.swing.Icon { + error("Swing Icons are not supported.") + } + private var dynamicIconNextId = 0 } diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/layers/ModifierHelpers.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/ModifierHelpers.kt index 3c82c05d52059..d6ecdbdf70192 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/layers/ModifierHelpers.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/layers/ModifierHelpers.kt @@ -1,12 +1,12 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.impl.layers -import org.jetbrains.icons.InternalIconsApi +import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.layers.IconLayer import org.jetbrains.icons.modifiers.CombinedIconModifier import org.jetbrains.icons.modifiers.IconModifier -@InternalIconsApi +@ApiStatus.Internal inline fun IconLayer.findModifier(): TModifier? { var output: TModifier? = null traverseModifiers(modifier) { @@ -19,7 +19,7 @@ inline fun IconLayer.findModifier(): TModifier return output } -@InternalIconsApi +@ApiStatus.Internal fun traverseModifiers(modifier: IconModifier, traverser: (IconModifier) -> Boolean): Boolean { if (traverser(modifier)) { if (modifier is CombinedIconModifier) { diff --git a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/AwtImageResource.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/AwtImageResource.kt similarity index 100% rename from platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/AwtImageResource.kt rename to platform/icons-impl/src/org/jetbrains/icons/impl/rendering/AwtImageResource.kt diff --git a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/CachedGPUImageResourceHolder.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/CachedGPUImageResourceHolder.kt similarity index 100% rename from platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/CachedGPUImageResourceHolder.kt rename to platform/icons-impl/src/org/jetbrains/icons/impl/rendering/CachedGPUImageResourceHolder.kt diff --git a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/CoroutineBasedMutableIconUpdateFlow.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/CoroutineBasedMutableIconUpdateFlow.kt similarity index 100% rename from platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/CoroutineBasedMutableIconUpdateFlow.kt rename to platform/icons-impl/src/org/jetbrains/icons/impl/rendering/CoroutineBasedMutableIconUpdateFlow.kt diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultDeferredIconRenderer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultDeferredIconRenderer.kt new file mode 100644 index 0000000000000..04c89e4fbc0ad --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultDeferredIconRenderer.kt @@ -0,0 +1,32 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.rendering + +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.icons.impl.DefaultDeferredIcon +import org.jetbrains.icons.rendering.Dimensions +import org.jetbrains.icons.rendering.IconRenderer +import org.jetbrains.icons.rendering.LoadingStrategy +import org.jetbrains.icons.rendering.PaintingApi +import org.jetbrains.icons.rendering.RenderingContext +import org.jetbrains.icons.rendering.ScalingContext +import org.jetbrains.icons.rendering.createRenderer + +// TODO Implement actual resolving & data transfer +internal class DefaultDeferredIconRenderer( + override val icon: DefaultDeferredIcon, + val renderingContext: RenderingContext, + loadingStrategy: LoadingStrategy +): IconRenderer { + private var currentIcon = icon.currentIcon + private var renderer = currentIcon?.createRenderer(renderingContext, loadingStrategy) + + @ApiStatus.Internal + override fun render(api: PaintingApi) { + renderer?.render(api) + } + + @ApiStatus.Internal + override fun calculateExpectedDimensions(scaling: ScalingContext): Dimensions { + return renderer?.calculateExpectedDimensions(scaling) ?: Dimensions(0, 0) + } +} diff --git a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultIconRenderer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultIconRenderer.kt similarity index 100% rename from platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultIconRenderer.kt rename to platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultIconRenderer.kt diff --git a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultIconRendererManager.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultIconRendererManager.kt similarity index 90% rename from platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultIconRendererManager.kt rename to platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultIconRendererManager.kt index 8d38fccf66238..47837c20c7949 100644 --- a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultIconRendererManager.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultIconRendererManager.kt @@ -16,7 +16,7 @@ import org.jetbrains.icons.layers.IconLayer import org.jetbrains.icons.impl.rendering.layers.IconLayerRenderer import org.jetbrains.icons.impl.rendering.layers.IconLayerManager import org.jetbrains.icons.rendering.RenderingContext -import org.jetbrains.icons.impl.DefaultDynamicIcon +import org.jetbrains.icons.impl.DefaultDeferredIcon import org.jetbrains.icons.impl.layers.ShapeIconLayer import org.jetbrains.icons.impl.rendering.layers.ShapeIconLayerRenderer import org.jetbrains.icons.impl.rendering.layers.ImageIconLayerRenderer @@ -33,8 +33,8 @@ abstract class DefaultIconRendererManager: IconRendererManager, IconLayerManager protected fun createRendererOrNull(icon: Icon, context: RenderingContext, loadingStrategy: LoadingStrategy): IconRenderer? { return when (icon) { - is DefaultLayeredIcon -> _root_ide_package_.org.jetbrains.icons.impl.rendering.DefaultIconRenderer(icon, context, loadingStrategy) - is DefaultDynamicIcon -> DefaultDynamicIconRenderer(icon, context, loadingStrategy) + is DefaultLayeredIcon -> DefaultIconRenderer(icon, context, loadingStrategy) + is DefaultDeferredIcon -> DefaultDeferredIconRenderer(icon, context, loadingStrategy) else -> null } } diff --git a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultImageModifiers.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultImageModifiers.kt similarity index 100% rename from platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultImageModifiers.kt rename to platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultImageModifiers.kt diff --git a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultImageResourceProvider.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultImageResourceProvider.kt similarity index 64% rename from platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultImageResourceProvider.kt rename to platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultImageResourceProvider.kt index 57b6ed101c521..e87e0ee7caeab 100644 --- a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/DefaultImageResourceProvider.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultImageResourceProvider.kt @@ -1,10 +1,11 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.impl.rendering import org.jetbrains.icons.rendering.ImageModifiers import org.jetbrains.icons.rendering.ImageResource import org.jetbrains.icons.rendering.ImageResourceProvider +import javax.swing.Icon abstract class DefaultImageResourceProvider: ImageResourceProvider { - abstract fun fromSwingIcon(icon: javax.swing.Icon, imageModifiers: ImageModifiers? = null): ImageResource + abstract fun fromSwingIcon(icon: Icon, imageModifiers: ImageModifiers? = null): ImageResource } \ No newline at end of file diff --git a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/MutableIconUpdateFlowBase.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/MutableIconUpdateFlowBase.kt similarity index 100% rename from platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/MutableIconUpdateFlowBase.kt rename to platform/icons-impl/src/org/jetbrains/icons/impl/rendering/MutableIconUpdateFlowBase.kt diff --git a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/SwingIcon.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingIcon.kt similarity index 96% rename from platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/SwingIcon.kt rename to platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingIcon.kt index 43d5723e85609..5edfe03434b11 100644 --- a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/SwingIcon.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingIcon.kt @@ -1,6 +1,7 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.impl.rendering +import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.Icon import org.jetbrains.icons.rendering.IconRendererManager import org.jetbrains.icons.rendering.RenderingContext @@ -11,6 +12,7 @@ import java.awt.Graphics import java.awt.Graphics2D import java.awt.image.BufferedImage +@ApiStatus.Experimental class SwingIcon( val icon: Icon ): javax.swing.Icon { diff --git a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/SwingPaintingApi.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingPaintingApi.kt similarity index 98% rename from platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/SwingPaintingApi.kt rename to platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingPaintingApi.kt index 55138e5315e54..18384160e5567 100644 --- a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/SwingPaintingApi.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingPaintingApi.kt @@ -17,6 +17,7 @@ import java.awt.Graphics import java.awt.Graphics2D import java.awt.Image import java.awt.RenderingHints +import java.awt.Shape import java.awt.geom.Ellipse2D import java.awt.geom.Rectangle2D import kotlin.text.toInt @@ -70,7 +71,7 @@ class SwingPaintingApi( drawShape(color, Rectangle2D.Double(x.toDouble(), y.toDouble(), width.toDouble(), height.toDouble()), alpha, mode) } - private fun drawShape(color: Color, shape: java.awt.Shape, alpha: Float, mode: DrawMode) { + private fun drawShape(color: Color, shape: Shape, alpha: Float, mode: DrawMode) { setDrawingDefaults() if (g is Graphics2D) { val oldComposite = g.composite diff --git a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/AnimatedIconLayerRenderer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/AnimatedIconLayerRenderer.kt similarity index 100% rename from platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/AnimatedIconLayerRenderer.kt rename to platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/AnimatedIconLayerRenderer.kt diff --git a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/DefaultLayerLayout.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/DefaultLayerLayout.kt similarity index 100% rename from platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/DefaultLayerLayout.kt rename to platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/DefaultLayerLayout.kt diff --git a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/IconIconLayerRenderer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/IconIconLayerRenderer.kt similarity index 93% rename from platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/IconIconLayerRenderer.kt rename to platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/IconIconLayerRenderer.kt index dc7cc4a914ea6..a5f0b3f0a446b 100644 --- a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/IconIconLayerRenderer.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/IconIconLayerRenderer.kt @@ -17,7 +17,7 @@ class IconIconLayerRenderer( private val renderer = layer.icon.createRenderer(renderingContext.adjustTo(layer)) override fun render(api: PaintingApi) { - val layout = _root_ide_package_.org.jetbrains.icons.impl.rendering.layers.DefaultLayerLayout( + val layout = DefaultLayerLayout( Bounds( 0, 0, diff --git a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/IconLayerManager.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/IconLayerManager.kt similarity index 100% rename from platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/IconLayerManager.kt rename to platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/IconLayerManager.kt diff --git a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/IconLayerRenderer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/IconLayerRenderer.kt similarity index 100% rename from platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/IconLayerRenderer.kt rename to platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/IconLayerRenderer.kt diff --git a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/ImageIconLayerRenderer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/ImageIconLayerRenderer.kt similarity index 95% rename from platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/ImageIconLayerRenderer.kt rename to platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/ImageIconLayerRenderer.kt index b99d1cc6da62a..4ab215d400cee 100644 --- a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/ImageIconLayerRenderer.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/ImageIconLayerRenderer.kt @@ -32,7 +32,7 @@ abstract class BaseImageIconLayerRenderer: IconLayerRenderer { val currentImage = image val w = api.scaling.applyTo(currentImage.width) val h = api.scaling.applyTo(currentImage.height) - val layout = _root_ide_package_.org.jetbrains.icons.impl.rendering.layers.DefaultLayerLayout( + val layout = DefaultLayerLayout( Bounds( 0, 0, diff --git a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/LayerHelpers.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/LayerHelpers.kt similarity index 100% rename from platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/LayerHelpers.kt rename to platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/LayerHelpers.kt diff --git a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/LayoutIconLayerRenderer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/LayoutIconLayerRenderer.kt similarity index 97% rename from platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/LayoutIconLayerRenderer.kt rename to platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/LayoutIconLayerRenderer.kt index 8a89e99bd27ed..21f5c4debaf39 100644 --- a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/LayoutIconLayerRenderer.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/LayoutIconLayerRenderer.kt @@ -24,7 +24,7 @@ class LayoutIconLayerRenderer( } override fun render(api: PaintingApi) { - val layout = _root_ide_package_.org.jetbrains.icons.impl.rendering.layers.DefaultLayerLayout( + val layout = DefaultLayerLayout( Bounds( 0, 0, diff --git a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/ShapeIconLayerRenderer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/ShapeIconLayerRenderer.kt similarity index 100% rename from platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/layers/ShapeIconLayerRenderer.kt rename to platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/ShapeIconLayerRenderer.kt diff --git a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/modifiers/ColorFilterModifier.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/modifiers/ColorFilterModifier.kt similarity index 100% rename from platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/modifiers/ColorFilterModifier.kt rename to platform/icons-impl/src/org/jetbrains/icons/impl/rendering/modifiers/ColorFilterModifier.kt diff --git a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/modifiers/IconModifier.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/modifiers/IconModifier.kt similarity index 96% rename from platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/modifiers/IconModifier.kt rename to platform/icons-impl/src/org/jetbrains/icons/impl/rendering/modifiers/IconModifier.kt index 96a33a9bb27cf..fc652da4539ed 100644 --- a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/modifiers/IconModifier.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/modifiers/IconModifier.kt @@ -1,8 +1,7 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.impl.rendering.modifiers -import org.jetbrains.icons.ExperimentalIconsApi -import org.jetbrains.icons.InternalIconsApi +import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.design.MaxIconUnit import org.jetbrains.icons.design.DisplayPointIconUnit import org.jetbrains.icons.design.IconUnit @@ -27,7 +26,7 @@ import org.jetbrains.icons.impl.rendering.layers.LayerLayout import org.jetbrains.icons.impl.rendering.layers.applyTo import kotlin.math.roundToInt -@OptIn(ExperimentalIconsApi::class, InternalIconsApi::class) +@ApiStatus.Internal fun IconModifier.applyTo(layout: LayerLayout, scaling: ScalingContext): LayerLayout { return when (this) { is CombinedIconModifier -> { diff --git a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/modifiers/MarginIconModifier.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/modifiers/MarginIconModifier.kt similarity index 100% rename from platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/modifiers/MarginIconModifier.kt rename to platform/icons-impl/src/org/jetbrains/icons/impl/rendering/modifiers/MarginIconModifier.kt diff --git a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/toAwtColor.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/toAwtColor.kt similarity index 79% rename from platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/toAwtColor.kt rename to platform/icons-impl/src/org/jetbrains/icons/impl/rendering/toAwtColor.kt index 267f5f389e615..d06d9bc5dae72 100644 --- a/platform/icons-impl/rendering/src/org/jetbrains/icons/impl/rendering/toAwtColor.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/toAwtColor.kt @@ -1,10 +1,9 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.impl.rendering -import org.jetbrains.icons.design.Color import org.jetbrains.icons.design.RGBA -fun Color.toAwtColor(): java.awt.Color { +fun org.jetbrains.icons.design.Color.toAwtColor(): java.awt.Color { @Suppress("UseJBColor") when (this) { is RGBA -> return java.awt.Color(red, green, blue, alpha) diff --git a/platform/ide-core/resources/META-INF/intellij.moduleSets.core.platform.xml b/platform/ide-core/resources/META-INF/intellij.moduleSets.core.platform.xml index bc91f307dac07..64750edee977d 100644 --- a/platform/ide-core/resources/META-INF/intellij.moduleSets.core.platform.xml +++ b/platform/ide-core/resources/META-INF/intellij.moduleSets.core.platform.xml @@ -132,7 +132,6 @@ - diff --git a/platform/jewel/ide-laf-bridge/BUILD.bazel b/platform/jewel/ide-laf-bridge/BUILD.bazel index 3702d62b1f7be..8511b12bfba2f 100644 --- a/platform/jewel/ide-laf-bridge/BUILD.bazel +++ b/platform/jewel/ide-laf-bridge/BUILD.bazel @@ -45,7 +45,6 @@ jvm_library( "//platform/core-ui", "//libraries/compose-foundation-desktop", "//platform/editor-ui-ex:editor-ex", - "//python:python-community-impl", ], exports = [ "//platform/jewel/foundation", diff --git a/platform/jewel/ide-laf-bridge/intellij.platform.jewel.ideLafBridge.iml b/platform/jewel/ide-laf-bridge/intellij.platform.jewel.ideLafBridge.iml index 5b3034e7f4b86..2fd98aefa08fa 100644 --- a/platform/jewel/ide-laf-bridge/intellij.platform.jewel.ideLafBridge.iml +++ b/platform/jewel/ide-laf-bridge/intellij.platform.jewel.ideLafBridge.iml @@ -51,7 +51,6 @@ - diff --git a/platform/jewel/ide-laf-bridge/src/main/resources/intellij.platform.jewel.ideLafBridge.xml b/platform/jewel/ide-laf-bridge/src/main/resources/intellij.platform.jewel.ideLafBridge.xml index 2212d890c6c5b..9cb39b531c856 100644 --- a/platform/jewel/ide-laf-bridge/src/main/resources/intellij.platform.jewel.ideLafBridge.xml +++ b/platform/jewel/ide-laf-bridge/src/main/resources/intellij.platform.jewel.ideLafBridge.xml @@ -1,7 +1,9 @@ - - - + + + + + diff --git a/platform/jewel/int-ui/int-ui-standalone/BUILD.bazel b/platform/jewel/int-ui/int-ui-standalone/BUILD.bazel index 9a514cf0de896..b73dbf72a4841 100644 --- a/platform/jewel/int-ui/int-ui-standalone/BUILD.bazel +++ b/platform/jewel/int-ui/int-ui-standalone/BUILD.bazel @@ -9,9 +9,6 @@ create_kotlinc_options( "androidx.compose.foundation.ExperimentalFoundationApi", "org.jetbrains.jewel.foundation.ExperimentalJewelApi", "org.jetbrains.jewel.foundation.InternalJewelApi", - "org.jetbrains.icons.api.InternalIconsApi", - "org.jetbrains.icons.InternalIconsApi", - "org.jetbrains.icons.ExperimentalIconsApi", ], x_context_parameters = True, x_explicit_api_mode = "strict" @@ -43,7 +40,6 @@ jvm_library( "@lib//:jna", "//platform/icons-api", "//platform/icons-impl", - "//platform/icons-impl/rendering", ], plugins = ["@lib//:compose-plugin"] ) diff --git a/platform/jewel/int-ui/int-ui-standalone/build.gradle.kts b/platform/jewel/int-ui/int-ui-standalone/build.gradle.kts index 61d0e68c4e241..226c8cb01a834 100644 --- a/platform/jewel/int-ui/int-ui-standalone/build.gradle.kts +++ b/platform/jewel/int-ui/int-ui-standalone/build.gradle.kts @@ -16,7 +16,6 @@ dependencies { api(project(":jb-icons-api")) api(project(":jb-icons-api-rendering")) api(project(":jb-icons-impl")) - api(project(":jb-icons-impl-rendering")) implementation(libs.jbr.api) implementation(libs.jna.core) } diff --git a/platform/jewel/int-ui/int-ui-standalone/intellij.platform.jewel.intUi.standalone.iml b/platform/jewel/int-ui/int-ui-standalone/intellij.platform.jewel.intUi.standalone.iml index 2541a3c2ba967..353c9fd888bfc 100644 --- a/platform/jewel/int-ui/int-ui-standalone/intellij.platform.jewel.intUi.standalone.iml +++ b/platform/jewel/int-ui/int-ui-standalone/intellij.platform.jewel.intUi.standalone.iml @@ -4,7 +4,7 @@ - @@ -44,6 +44,5 @@ - \ No newline at end of file diff --git a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconRendererManager.kt b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconRendererManager.kt index f0c2649d93e4d..556c217c43c2a 100644 --- a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconRendererManager.kt +++ b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconRendererManager.kt @@ -1,12 +1,10 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -@file:OptIn(ExperimentalIconsApi::class) package org.jetbrains.jewel.intui.standalone.icon import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.FlowCollector -import org.jetbrains.icons.ExperimentalIconsApi import org.jetbrains.icons.Icon import org.jetbrains.icons.impl.rendering.CoroutineBasedMutableIconUpdateFlow import org.jetbrains.icons.impl.rendering.DefaultIconRendererManager @@ -14,7 +12,6 @@ import org.jetbrains.icons.rendering.ImageModifiers import org.jetbrains.icons.rendering.MutableIconUpdateFlow import org.jetbrains.icons.rendering.RenderingContext -@OptIn(ExperimentalIconsApi::class) internal class StandaloneIconRendererManager : DefaultIconRendererManager() { override fun createUpdateFlow(scope: CoroutineScope?, updateCallback: (Int) -> Unit): MutableIconUpdateFlow { if (scope == null) return EmptyMutableIconUpdateFlow() diff --git a/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/ShowcaseIcons.kt b/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/ShowcaseIcons.kt index 14fd8afc2ccdd..cbc18260e37bd 100644 --- a/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/ShowcaseIcons.kt +++ b/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/ShowcaseIcons.kt @@ -15,22 +15,10 @@ import org.jetbrains.jewel.ui.icon.PathIconKey public object ShowcaseIcons { public val componentsMenu: PathIconKey = PathIconKey("icons/structure.svg", ShowcaseIcons::class.java) - public val gitHub: Icon = imageIcon("icons/github.svg", ShowcaseIcons::class.java.classLoader, modifier = IconModifier.fillMaxSize()) - public val themeDark: Icon = imageIcon("icons/darkTheme.svg", ShowcaseIcons::class.java.classLoader) - public val jewelLogo: Icon = imageIcon("icons/systemTheme.svg", ShowcaseIcons::class.java.classLoader, modifier = IconModifier.fillMaxSize()) - public val layeredIcon: Icon = icon { - icon(gitHub, IconModifier.fillMaxSize()) - icon( - jewelLogo, - modifier = IconModifier.align(IconAlign.BottomRight) - .size(40.percent) - .margin(25.percent, 0.percent, 0.percent, 0.percent) - ) - } - public val gitHubKey: PathIconKey = PathIconKey("icons/github.svg", ShowcaseIcons::class.java) - public val jewelLogoKey: PathIconKey = PathIconKey("icons/jewel-logo.svg", ShowcaseIcons::class.java) + public val jewelLogo: PathIconKey = PathIconKey("icons/jewel-logo.svg", ShowcaseIcons::class.java) + public val gitHub: PathIconKey = PathIconKey("icons/github.svg", ShowcaseIcons::class.java) public val markdown: PathIconKey = PathIconKey("icons/markdown.svg", ShowcaseIcons::class.java) - public val themeDarkKey: PathIconKey = PathIconKey("icons/darkTheme.svg", ShowcaseIcons::class.java) + public val themeDark: PathIconKey = PathIconKey("icons/darkTheme.svg", ShowcaseIcons::class.java) public val themeLight: PathIconKey = PathIconKey("icons/lightTheme.svg", ShowcaseIcons::class.java) public val themeLightWithLightHeader: PathIconKey = PathIconKey("icons/lightWithLightHeaderTheme.svg", ShowcaseIcons::class.java) diff --git a/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/components/Icons.kt b/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/components/Icons.kt index 1010af604ada7..ee71ce8680d81 100644 --- a/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/components/Icons.kt +++ b/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/components/Icons.kt @@ -51,12 +51,12 @@ public fun Icons(modifier: Modifier = Modifier) { modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp), ) { - Icon(ShowcaseIcons.jewelLogoKey, null, Modifier.size(16.dp)) - Icon(ShowcaseIcons.jewelLogoKey, "Jewel Logo", Modifier.size(32.dp)) - Icon(ShowcaseIcons.jewelLogoKey, "Jewel Logo", Modifier.size(64.dp)) - Icon(ShowcaseIcons.jewelLogoKey, "Jewel Logo", Modifier.size(128.dp)) + Icon(ShowcaseIcons.jewelLogo, null, Modifier.size(16.dp)) + Icon(ShowcaseIcons.jewelLogo, "Jewel Logo", Modifier.size(32.dp)) + Icon(ShowcaseIcons.jewelLogo, "Jewel Logo", Modifier.size(64.dp)) + Icon(ShowcaseIcons.jewelLogo, "Jewel Logo", Modifier.size(128.dp)) Icon( - key = ShowcaseIcons.jewelLogoKey, + key = ShowcaseIcons.jewelLogo, contentDescription = "Jewel Logo", modifier = Modifier.size(128.dp), colorFilter = ColorFilter.tint(Color.Magenta, BlendMode.Multiply), diff --git a/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/TitleBarView.kt b/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/TitleBarView.kt index 5dbed4561916e..46ca9bda8235d 100644 --- a/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/TitleBarView.kt +++ b/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/TitleBarView.kt @@ -12,7 +12,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import java.awt.Desktop import java.net.URI -import org.jetbrains.icons.ExperimentalIconsApi import org.jetbrains.icons.design.px import org.jetbrains.icons.icon import org.jetbrains.icons.modifiers.IconModifier @@ -31,7 +30,6 @@ import org.jetbrains.jewel.window.DecoratedWindowScope import org.jetbrains.jewel.window.TitleBar import org.jetbrains.jewel.window.newFullscreenControls -@OptIn(ExperimentalIconsApi::class) @ExperimentalLayoutApi @Composable internal fun DecoratedWindowScope.TitleBarView() { @@ -81,7 +79,7 @@ internal fun DecoratedWindowScope.TitleBarView() { { Desktop.getDesktop().browse(URI.create(jewelGithubLink)) }, Modifier.size(40.dp).padding(5.dp), ) { - Icon(ShowcaseIcons.gitHub, "Github") + Icon(ShowcaseIcons.gitHubKey, "Github") } } @@ -123,9 +121,7 @@ internal fun DecoratedWindowScope.TitleBarView() { ) IntUiThemes.Dark -> - Icon(icon { - icon(ShowcaseIcons.themeDark, modifier = IconModifier.size(20.px)) - }, contentDescription = "Dark") + Icon(ShowcaseIcons.themeDarkKey, contentDescription = "Dark") IntUiThemes.System -> Icon( diff --git a/platform/jewel/settings.gradle.kts b/platform/jewel/settings.gradle.kts index c37b16a17efe6..1b7165d7cc2c4 100644 --- a/platform/jewel/settings.gradle.kts +++ b/platform/jewel/settings.gradle.kts @@ -53,18 +53,12 @@ include( ":ui-tests", ":jb-icons-api", ":jb-icons-api-rendering", - ":jb-icons-api-rendering-lowlevel", - ":jb-icons-impl", - ":jb-icons-impl-rendering", - ":jb-icons-legacy-icon-support" + ":jb-icons-impl" ) project(":jb-icons-api").projectDir = file("../icons-api") project(":jb-icons-api-rendering").projectDir = file("../icons-api/rendering") -project(":jb-icons-api-rendering-lowlevel").projectDir = file("../icons-api/rendering/lowlevel") -project(":jb-icons-legacy-icon-support").projectDir = file("../icons-api/legacy-icon-support") project(":jb-icons-impl").projectDir = file("../icons-impl") -project(":jb-icons-impl-rendering").projectDir = file("../icons-impl/rendering") develocity { diff --git a/platform/jewel/ui/BUILD.bazel b/platform/jewel/ui/BUILD.bazel index a6b78bf7a613b..3bcc650fbeebb 100644 --- a/platform/jewel/ui/BUILD.bazel +++ b/platform/jewel/ui/BUILD.bazel @@ -9,9 +9,6 @@ create_kotlinc_options( "androidx.compose.foundation.ExperimentalFoundationApi", "org.jetbrains.jewel.foundation.ExperimentalJewelApi", "org.jetbrains.jewel.foundation.InternalJewelApi", - "org.jetbrains.icons.api.InternalIconsApi", - "org.jetbrains.icons.ExperimentalIconsApi", - "org.jetbrains.icons.InternalIconsApi", ], x_context_parameters = True, x_explicit_api_mode = "strict" @@ -48,9 +45,7 @@ jvm_library( "//libraries/compose-runtime-desktop", "//platform/icons-api", "//platform/icons-api/rendering", - "//platform/icons-api/rendering/lowlevel", "//platform/icons-impl", - "//platform/icons-impl/rendering", ], exports = [ "//platform/jewel/foundation", @@ -89,9 +84,7 @@ jvm_library( "//libraries/junit4:junit4_test_lib", "//platform/icons-api", "//platform/icons-api/rendering", - "//platform/icons-api/rendering/lowlevel", "//platform/icons-impl", - "//platform/icons-impl/rendering", "//libraries/compose-foundation-desktop-junit", "//libraries/compose-foundation-desktop-junit:compose-foundation-desktop-junit_test_lib", ], diff --git a/platform/jewel/ui/build.gradle.kts b/platform/jewel/ui/build.gradle.kts index 9b7348fce3c37..87569ff734071 100644 --- a/platform/jewel/ui/build.gradle.kts +++ b/platform/jewel/ui/build.gradle.kts @@ -15,9 +15,7 @@ dependencies { api(projects.foundation) api(project(":jb-icons-api")) api(project(":jb-icons-api-rendering")) - api(project(":jb-icons-api-rendering-lowlevel")) api(project(":jb-icons-impl")) - api(project(":jb-icons-impl-rendering")) implementation(compose.components.resources) testImplementation(compose.desktop.uiTestJUnit4) testImplementation(compose.desktop.currentOs) { exclude(group = "org.jetbrains.compose.material") } diff --git a/platform/jewel/ui/intellij.platform.jewel.ui.iml b/platform/jewel/ui/intellij.platform.jewel.ui.iml index e39a2bb8ef8a2..19206e157e095 100644 --- a/platform/jewel/ui/intellij.platform.jewel.ui.iml +++ b/platform/jewel/ui/intellij.platform.jewel.ui.iml @@ -4,7 +4,7 @@ - @@ -84,9 +84,7 @@ - - \ No newline at end of file diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeImageResourceProvider.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeImageResourceProvider.kt index 7bff889bdd8d7..0769389be795a 100644 --- a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeImageResourceProvider.kt +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeImageResourceProvider.kt @@ -10,8 +10,6 @@ import javax.xml.XMLConstants import javax.xml.parsers.DocumentBuilderFactory import org.jetbrains.annotations.ApiStatus import org.jetbrains.compose.resources.decodeToSvgPainter -import org.jetbrains.icons.ExperimentalIconsApi -import org.jetbrains.icons.InternalIconsApi import org.jetbrains.icons.modifiers.svgPatcher import org.jetbrains.icons.patchers.SvgPatchOperation import org.jetbrains.icons.patchers.SvgPatcher @@ -25,10 +23,8 @@ import org.jetbrains.jewel.foundation.InternalJewelApi import org.jetbrains.jewel.ui.painter.writeToString import org.w3c.dom.Element -@OptIn(ExperimentalIconsApi::class) @InternalJewelApi @ApiStatus.Internal -@InternalIconsApi public class ComposeImageResourceProvider : ImageResourceProvider { override fun loadImage(location: ImageResourceLocation, imageModifiers: ImageModifiers?): ImageResource { // TODO Support image modifiers @@ -50,7 +46,6 @@ public class ComposeImageResourceProvider : ImageResourceProvider { private val documentBuilderFactory = DocumentBuilderFactory.newDefaultInstance().apply { setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true) } -@InternalIconsApi private fun patchSvg(modifiers: ImageModifiers?, inputStream: InputStream): ByteArray { val builder = documentBuilderFactory.newDocumentBuilder() val document = builder.parse(inputStream) diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/IconKey.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/IconKey.kt index aaa19acdcf985..3abfba01e95bd 100644 --- a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/IconKey.kt +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/IconKey.kt @@ -1,6 +1,6 @@ package org.jetbrains.jewel.ui.icon -import org.jetbrains.icons.ExperimentalIconsApi +import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.design.IconDesigner import org.jetbrains.icons.modifiers.IconModifier import org.jetbrains.jewel.foundation.GenerateDataFunctions @@ -11,7 +11,7 @@ public interface IconKey { public fun path(isNewUi: Boolean): String } -@ExperimentalIconsApi +@ApiStatus.Experimental public fun IconDesigner.iconKey(iconKey: IconKey, modifier: IconModifier = IconModifier) { image(iconKey.path(isNewUi = true), iconKey.iconClass.classLoader, modifier) } diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ResourceImageIconLoader.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/PathImageResourceLocation.kt similarity index 100% rename from platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ResourceImageIconLoader.kt rename to platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/PathImageResourceLocation.kt diff --git a/platform/jewel/ui/src/main/resources/intellij.platform.jewel.ui.xml b/platform/jewel/ui/src/main/resources/intellij.platform.jewel.ui.xml index e08b82484c815..cd995c662cfe6 100644 --- a/platform/jewel/ui/src/main/resources/intellij.platform.jewel.ui.xml +++ b/platform/jewel/ui/src/main/resources/intellij.platform.jewel.ui.xml @@ -1,7 +1,13 @@ - + + + + + + + diff --git a/platform/platform-impl/bootstrap/BUILD.bazel b/platform/platform-impl/bootstrap/BUILD.bazel index 411adbc14789e..dec24e1789086 100644 --- a/platform/platform-impl/bootstrap/BUILD.bazel +++ b/platform/platform-impl/bootstrap/BUILD.bazel @@ -48,8 +48,6 @@ jvm_library( "//platform/eel-provider", "//platform/icons-impl", "//platform/icons-impl/intellij", - "//platform/icons-impl/rendering", - "//platform/icons-impl/intellij/rendering", ] ) diff --git a/platform/platform-impl/bootstrap/intellij.platform.ide.bootstrap.iml b/platform/platform-impl/bootstrap/intellij.platform.ide.bootstrap.iml index f80d0dbab4550..59decad7ed82e 100644 --- a/platform/platform-impl/bootstrap/intellij.platform.ide.bootstrap.iml +++ b/platform/platform-impl/bootstrap/intellij.platform.ide.bootstrap.iml @@ -48,7 +48,5 @@ - - \ No newline at end of file diff --git a/platform/platform-impl/bootstrap/src/com/intellij/platform/ide/bootstrap/ui.kt b/platform/platform-impl/bootstrap/src/com/intellij/platform/ide/bootstrap/ui.kt index 22423c3d977e9..b1670ea8f2d21 100644 --- a/platform/platform-impl/bootstrap/src/com/intellij/platform/ide/bootstrap/ui.kt +++ b/platform/platform-impl/bootstrap/src/com/intellij/platform/ide/bootstrap/ui.kt @@ -34,13 +34,9 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.jetbrains.annotations.VisibleForTesting -import org.jetbrains.icons.ExperimentalIconsApi -import org.jetbrains.icons.InternalIconsApi import org.jetbrains.icons.impl.intellij.IntelliJIconManager import org.jetbrains.icons.impl.intellij.rendering.IntelliJIconRendererManager import org.jetbrains.icons.impl.intellij.rendering.images.IntelliJImageResourceProvider -import org.jetbrains.icons.impl.rendering.DefaultSwingIconManager -import org.jetbrains.icons.legacyIconSupport.SwingIconManager import org.jetbrains.icons.rendering.IconRendererManager import org.jetbrains.icons.rendering.ImageResourceProvider import java.awt.Font @@ -53,7 +49,6 @@ import javax.swing.RepaintManager import javax.swing.UIManager import kotlin.system.exitProcess -@OptIn(ExperimentalIconsApi::class, InternalIconsApi::class) internal suspend fun initUi(initAwtToolkitJob: Job, isHeadless: Boolean, asyncScope: CoroutineScope) { // IdeaLaF uses AllIcons - icon manager must be activated if (!isHeadless) { @@ -65,7 +60,6 @@ internal suspend fun initUi(initAwtToolkitJob: Job, isHeadless: Boolean, asyncSc org.jetbrains.icons.IconManager.activate(IntelliJIconManager()) IconRendererManager.activate(IntelliJIconRendererManager()) ImageResourceProvider.activate(IntelliJImageResourceProvider()) - SwingIconManager.activate(DefaultSwingIconManager()) } } diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.ide.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.ide.xml index 7d9b8681ab1d6..40f3a892dd9e3 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.ide.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.ide.xml @@ -133,7 +133,6 @@ - diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.lang.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.lang.xml index e533aa0bc95f0..088997f79ccce 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.lang.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.lang.xml @@ -134,7 +134,6 @@ - @@ -174,7 +173,6 @@ - diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.minimal.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.minimal.xml index fcc5009c6c897..ecf55f86773c7 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.minimal.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.minimal.xml @@ -135,7 +135,6 @@ - @@ -175,7 +174,6 @@ - diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.xml index f3bd2b794b2e9..9f2c1b1baa1e0 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.xml @@ -136,7 +136,6 @@ - @@ -176,7 +175,6 @@ - diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.ide.common.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.ide.common.xml index 41c71efadea4b..86d2537eaaab0 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.ide.common.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.ide.common.xml @@ -139,7 +139,6 @@ - @@ -179,7 +178,6 @@ - diff --git a/platform/util/BUILD.bazel b/platform/util/BUILD.bazel index 8a216ced6e2e1..a40a1b429590d 100644 --- a/platform/util/BUILD.bazel +++ b/platform/util/BUILD.bazel @@ -42,7 +42,6 @@ jvm_library( "//platform/eel", "//platform/icons-api", "//platform/icons-api/rendering", - "//platform/icons-api/legacy-icon-support", ":platform_util_troveCompileOnly_provided", ], exports = [ @@ -52,7 +51,6 @@ jvm_library( "//platform/util/multiplatform", "//platform/icons-api", "//platform/icons-api/rendering", - "//platform/icons-api/legacy-icon-support", ], runtime_deps = [ "//libraries/commons/io", diff --git a/platform/util/intellij.platform.util.iml b/platform/util/intellij.platform.util.iml index 78b686121859a..7aab8e448e7b5 100644 --- a/platform/util/intellij.platform.util.iml +++ b/platform/util/intellij.platform.util.iml @@ -56,7 +56,6 @@ - diff --git a/platform/util/ui/src/com/intellij/ui/icons/CachedImageIcon.kt b/platform/util/ui/src/com/intellij/ui/icons/CachedImageIcon.kt index a349bcd48745f..5013348f72379 100644 --- a/platform/util/ui/src/com/intellij/ui/icons/CachedImageIcon.kt +++ b/platform/util/ui/src/com/intellij/ui/icons/CachedImageIcon.kt @@ -172,8 +172,7 @@ open class CachedImageIcon private constructor( return (image as? JBHiDPIScaledImage)?.delegate ?: image } - @Internal - fun resolveImage(scaleContext: ScaleContext?): Image? { + internal fun resolveImage(scaleContext: ScaleContext?): Image? { val icon = if (scaleContext == null) resolveActualIcon() else resolveActualIcon(scaleContext) return if (icon is ScaledResultIcon) icon.image else null } diff --git a/platform/util/ui/src/com/intellij/ui/icons/LegacyIcon.kt b/platform/util/ui/src/com/intellij/ui/icons/LegacyIcon.kt deleted file mode 100644 index 89fefa2a31b8e..0000000000000 --- a/platform/util/ui/src/com/intellij/ui/icons/LegacyIcon.kt +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -/*package com.intellij.ui.icons - -import org.jetbrains.icons.api.IconIdentifier -import org.jetbrains.icons.api.customIconIdentifier -import java.util.UUID - -interface LegacySwingIcon : org.jetbrains.icons.api.Icon, javax.swing.Icon { - val swingIcon: javax.swing.Icon - - override fun paintIcon(c: java.awt.Component, g: java.awt.Graphics, x: Int, y: Int) { - swingIcon.paintIcon(c, g, x, y) - } - override fun getIconWidth(): Int = swingIcon.iconWidth - override fun getIconHeight(): Int = swingIcon.iconHeight -} - -interface LegacyIcon : LegacySwingIcon { - override val swingIcon: TIcon -} - -private class LegacyIconImpl( - override val identifier: IconIdentifier, - override val swingIcon: TIcon -): LegacyIcon - -fun TIcon.legacyIcon(): LegacyIcon { - val identifier = if (this is CachedImageIcon) { - customIconIdentifier("::legacy/CachedImageIcon/") - } else customIconIdentifier("::legacy/unknown/" + UUID.randomUUID().toString()) - return LegacyIconImpl( - identifier, - this - ) -}*/ \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.compose/BUILD.bazel b/plugins/devkit/intellij.devkit.compose/BUILD.bazel index 2cd7f63758021..b60e41afa3ac7 100644 --- a/plugins/devkit/intellij.devkit.compose/BUILD.bazel +++ b/plugins/devkit/intellij.devkit.compose/BUILD.bazel @@ -64,7 +64,6 @@ jvm_library( "//platform/util/jdom", "//platform/external-system-impl:externalSystem-impl", "//plugins/gradle", - "//platform/icons-api/legacy-icon-support", "//platform/platform-impl/rpc", "//fleet/util/core", "//libraries/kotlinx/serialization/json", diff --git a/plugins/devkit/intellij.devkit.compose/intellij.devkit.compose.iml b/plugins/devkit/intellij.devkit.compose/intellij.devkit.compose.iml index 91ec40251c1b8..966710d185fd0 100644 --- a/plugins/devkit/intellij.devkit.compose/intellij.devkit.compose.iml +++ b/plugins/devkit/intellij.devkit.compose/intellij.devkit.compose.iml @@ -68,7 +68,6 @@ - diff --git a/plugins/devkit/intellij.devkit.compose/src/showcase/ComposePerformanceDemoAction.kt b/plugins/devkit/intellij.devkit.compose/src/showcase/ComposePerformanceDemoAction.kt index 8a332f2da66bc..c6abe63d2db48 100644 --- a/plugins/devkit/intellij.devkit.compose/src/showcase/ComposePerformanceDemoAction.kt +++ b/plugins/devkit/intellij.devkit.compose/src/showcase/ComposePerformanceDemoAction.kt @@ -43,8 +43,7 @@ import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.project.Project import com.intellij.openapi.ui.DialogWrapper -import org.jetbrains.icons.ExperimentalIconsApi -import org.jetbrains.icons.legacyIconSupport.toNewIcon +import org.jetbrains.icons.swing.toNewIcon import org.jetbrains.jewel.bridge.compose import org.jetbrains.jewel.ui.component.Checkbox import org.jetbrains.jewel.ui.component.Icon @@ -169,7 +168,6 @@ private class MyDialog(project: Project?, dialogTitle: String) : } } -@OptIn(ExperimentalIconsApi::class) private fun createIconsComponent(): JComponent { return compose { var minFps by remember { mutableStateOf(Int.MAX_VALUE) } diff --git a/plugins/devkit/intellij.devkit.compose/src/showcase/Icons.kt b/plugins/devkit/intellij.devkit.compose/src/showcase/Icons.kt index f0741553e8cfa..95600b326cb75 100644 --- a/plugins/devkit/intellij.devkit.compose/src/showcase/Icons.kt +++ b/plugins/devkit/intellij.devkit.compose/src/showcase/Icons.kt @@ -1,27 +1,19 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -@file:OptIn(ExperimentalIconsApi::class) - package com.intellij.devkit.compose.showcase import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.requiredWidth -import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.awt.SwingPanel import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import com.intellij.icons.AllIcons -import com.intellij.ide.rpc.deserializeFromRpc -import com.intellij.ide.rpc.serializeToRpc import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir import com.intellij.openapi.vfs.findFile @@ -30,19 +22,17 @@ import com.intellij.ui.BadgeIcon import com.intellij.ui.SpinningProgressIcon import com.intellij.util.IconUtil import kotlinx.coroutines.delay -import kotlinx.coroutines.launch import kotlinx.serialization.json.Json -import org.jetbrains.icons.ExperimentalIconsApi import org.jetbrains.icons.Icon import org.jetbrains.icons.IconManager +import org.jetbrains.icons.deferredIconAsync import org.jetbrains.icons.design.Circle import org.jetbrains.icons.design.IconAlign import org.jetbrains.icons.design.percent -import org.jetbrains.icons.dynamicIcon import org.jetbrains.icons.icon -import org.jetbrains.icons.legacyIconSupport.swingIcon -import org.jetbrains.icons.legacyIconSupport.toNewIcon -import org.jetbrains.icons.legacyIconSupport.toSwingIcon +import org.jetbrains.icons.swing.swingIcon +import org.jetbrains.icons.swing.toNewIcon +import org.jetbrains.icons.swing.toSwingIcon import org.jetbrains.icons.modifiers.IconModifier import org.jetbrains.icons.modifiers.align import org.jetbrains.icons.modifiers.fillMaxSize @@ -159,19 +149,24 @@ internal fun Icons(project: Project) { }) val json = Json { serializersModule = IconManager.getInstance().getSerializersModule() } - val dynIcon = dynamicIcon(missingIcon) - val serialized = json.encodeToString(dynIcon) - val deserialized = json.decodeFromString(serialized) - val greenIcon = icon { - icon(missingIcon, modifier = IconModifier.patchSvg { - replaceUnlessMatches("fill", "white", "green") - }) - } - val scope = rememberCoroutineScope() - scope.launch { + val dynIcon = deferredIconAsync(missingIcon) { delay(5000) - dynIcon.swap(greenIcon) + icon { + icon(missingIcon, modifier = IconModifier.patchSvg { + replaceUnlessMatches("fill", "white", "green") + }) + } } + val serialized = json.encodeToString(dynIcon) + val deserialized = json.decodeFromString(serialized) + + icons.add(remember { + ShowcaseIcon( + dynIcon, + null, + "Dynamic Icon" + ) + }) icons.add(remember { ShowcaseIcon( From 72d5a370d907e489709a4b661939226cc910a26c Mon Sep 17 00:00:00 2001 From: Jakub Senohrabek Date: Thu, 19 Feb 2026 18:01:03 +0100 Subject: [PATCH 8/9] IJPL-176416 New Icons API - deferred icons & module removal GitOrigin-RevId: 51208a5aaaf467cd4d9b43d2d9ab1b2d8147d110 --- .../build/productLayout/CoreModuleSets.kt | 4 - .../resources/intellij.platform.core.ui.xml | 1 - platform/icons-api/BUILD.bazel | 19 +---- .../icons-api/intellij.platform.icons.api.iml | 11 ++- platform/icons-api/rendering/BUILD.bazel | 19 +---- .../intellij.platform.icons.api.rendering.iml | 11 ++- .../intellij.platform.icons.api.rendering.xml | 9 -- .../resources/intellij.platform.icons.api.xml | 9 -- .../src/org/jetbrains/icons/DeferredIcon.kt | 22 +++-- .../src/org/jetbrains/icons/IconIdentifier.kt | 4 + .../src/org/jetbrains/icons/IconManager.kt | 21 ++--- platform/icons-impl/BUILD.bazel | 19 +---- .../intellij.platform.icons.impl.iml | 11 ++- platform/icons-impl/intellij/BUILD.bazel | 24 +++--- .../intellij.platform.icons.impl.intellij.iml | 17 ++-- .../intellij.platform.icons.impl.intellij.xml | 18 ++-- .../IntelliJDeferredIconResolverService.kt | 29 +++++++ .../impl/intellij/IntelliJIconManager.kt | 84 +++++++++++++++++++ .../impl/intellij/ModuleIconIdentifier.kt | 36 ++++++++ .../intellij/ModuleImageResourceLocation.kt | 10 +-- .../custom/CustomIconLayerRegistration.kt | 2 +- .../custom/CustomLegacyIconSerializer.kt | 2 +- .../impl/intellij/rendering/Convertors.kt | 1 - .../intellij/rendering/IconUpdateService.kt | 1 - .../rendering/IntelliJIconRendererManager.kt | 2 - .../impl/intellij}/rendering/SwingIcon.kt | 2 +- .../rendering/SwingIconLayerRenderer.kt | 62 +++++++++++--- .../intellij}/rendering/SwingPaintingApi.kt | 6 +- .../custom/CustomIconLayerRendererProvider.kt | 2 +- .../rendering/images}/AwtImageResource.kt | 3 +- .../rendering/images/IntelliJImageResource.kt | 1 - .../impl/intellij}/rendering/toAwtColor.kt | 5 +- .../icons/impl/intellij/DynamicIconTest.kt | 1 - .../intellij.platform.icons.impl.xml | 10 --- .../icons-impl/src/StringIconIdentifier.kt | 26 ++++++ .../icons/impl/DefaultDeferredIcon.kt | 67 ++++++++++++++- .../icons/impl/DefaultIconManager.kt | 71 ++++++++++++++-- .../icons/impl/DeferredIconResolverService.kt | 82 ++++++++++++++++++ .../icons/impl/InPlaceDeferredIconResolver.kt | 36 ++++++++ .../rendering/DefaultDeferredIconRenderer.kt | 27 ++++-- .../rendering/DefaultIconRendererManager.kt | 8 +- .../intellij.moduleSets.core.platform.xml | 4 - .../standalone/icon/StandaloneIconManager.kt | 17 +++- .../intui/standalone/theme/IntUiTheme.kt | 5 ++ .../services/org.jetbrains.icons.IconManager | 1 - ...tellij.platform.jewel.intUi.standalone.xml | 3 - .../samples/showcase/components/Icons.kt | 4 +- .../samples/standalone/view/TitleBarView.kt | 4 +- .../samples/standalone/view/WelcomeView.kt | 4 +- .../resources/intellij.platform.jewel.ui.xml | 4 - .../META-INF/intellij.moduleSets.core.ide.xml | 4 - .../intellij.moduleSets.core.lang.xml | 7 +- .../intellij.moduleSets.essential.minimal.xml | 7 +- .../intellij.moduleSets.essential.xml | 7 +- .../intellij.moduleSets.ide.common.xml | 7 +- .../intellij.devkit.compose/BUILD.bazel | 1 - .../intellij.devkit.compose.iml | 2 +- .../src/showcase/ComposeShowcase.kt | 41 ++++++++- .../src/showcase/Icons.kt | 10 +-- 59 files changed, 655 insertions(+), 272 deletions(-) delete mode 100644 platform/icons-api/rendering/resources/intellij.platform.icons.api.rendering.xml delete mode 100644 platform/icons-api/resources/intellij.platform.icons.api.xml create mode 100644 platform/icons-api/src/org/jetbrains/icons/IconIdentifier.kt create mode 100644 platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJDeferredIconResolverService.kt create mode 100644 platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/ModuleIconIdentifier.kt rename platform/icons-impl/{src/org/jetbrains/icons/impl => intellij/src/org/jetbrains/icons/impl/intellij}/rendering/SwingIcon.kt (97%) rename platform/icons-impl/{src/org/jetbrains/icons/impl => intellij/src/org/jetbrains/icons/impl/intellij}/rendering/SwingPaintingApi.kt (97%) rename platform/icons-impl/{src/org/jetbrains/icons/impl/rendering => intellij/src/org/jetbrains/icons/impl/intellij/rendering/images}/AwtImageResource.kt (95%) rename platform/icons-impl/{src/org/jetbrains/icons/impl => intellij/src/org/jetbrains/icons/impl/intellij}/rendering/toAwtColor.kt (67%) delete mode 100644 platform/icons-impl/resources/intellij.platform.icons.impl.xml create mode 100644 platform/icons-impl/src/StringIconIdentifier.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/DeferredIconResolverService.kt create mode 100644 platform/icons-impl/src/org/jetbrains/icons/impl/InPlaceDeferredIconResolver.kt delete mode 100644 platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.IconManager diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CoreModuleSets.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CoreModuleSets.kt index c0661f491cabe..a12f303805ca7 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CoreModuleSets.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CoreModuleSets.kt @@ -270,9 +270,6 @@ object CoreModuleSets { embeddedModule("intellij.platform.analysis") embeddedModule("intellij.platform.analysis.impl") - embeddedModule("intellij.platform.icons.api") - embeddedModule("intellij.platform.icons.api.rendering") - // Include minimal RPC infrastructure AFTER core platform modules // (kernel depends on platform.core, so core must be available first) moduleSet(rpcMinimal()) @@ -346,7 +343,6 @@ object CoreModuleSets { // IDE implementation (depends on lang.core, so must come after) embeddedModule("intellij.platform.ide.impl") - embeddedModule("intellij.platform.icons.impl") embeddedModule("intellij.platform.icons.impl.intellij") // Additional dependencies specific to lang.impl and ide.impl diff --git a/platform/core-ui/resources/intellij.platform.core.ui.xml b/platform/core-ui/resources/intellij.platform.core.ui.xml index 39f239d9bb192..11fc9299a69f9 100644 --- a/platform/core-ui/resources/intellij.platform.core.ui.xml +++ b/platform/core-ui/resources/intellij.platform.core.ui.xml @@ -3,7 +3,6 @@ - diff --git a/platform/icons-api/BUILD.bazel b/platform/icons-api/BUILD.bazel index 5741fde9be103..17ff9b751c4c3 100644 --- a/platform/icons-api/BUILD.bazel +++ b/platform/icons-api/BUILD.bazel @@ -1,28 +1,11 @@ ### auto-generated section `build intellij.platform.icons.api` start -load("//build:compiler-options.bzl", "create_kotlinc_options") -load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") - -create_kotlinc_options( - name = "custom_icons-api", - api_version = "2.2", - language_version = "2.2", - progressive = False, - x_x_language = [] -) - -resourcegroup( - name = "icons-api_resources", - srcs = glob(["resources/**/*"]), - strip_prefix = "resources" -) +load("@rules_jvm//:jvm.bzl", "jvm_library") jvm_library( name = "icons-api", module_name = "intellij.platform.icons.api", visibility = ["//visibility:public"], srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), - resources = [":icons-api_resources"], - kotlinc_opts = ":custom_icons-api", deps = [ "@lib//:kotlin-stdlib", "//libraries/kotlinx/coroutines/core", diff --git a/platform/icons-api/intellij.platform.icons.api.iml b/platform/icons-api/intellij.platform.icons.api.iml index 5da8acae12762..7499edef5be6b 100644 --- a/platform/icons-api/intellij.platform.icons.api.iml +++ b/platform/icons-api/intellij.platform.icons.api.iml @@ -2,15 +2,15 @@ - + - - - - + + + @@ -25,7 +25,6 @@ - diff --git a/platform/icons-api/rendering/BUILD.bazel b/platform/icons-api/rendering/BUILD.bazel index 3c5330fcd97aa..a2797ddd5f23b 100644 --- a/platform/icons-api/rendering/BUILD.bazel +++ b/platform/icons-api/rendering/BUILD.bazel @@ -1,28 +1,11 @@ ### auto-generated section `build intellij.platform.icons.api.rendering` start -load("//build:compiler-options.bzl", "create_kotlinc_options") -load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") - -create_kotlinc_options( - name = "custom_rendering", - api_version = "2.2", - language_version = "2.2", - progressive = False, - x_x_language = [] -) - -resourcegroup( - name = "rendering_resources", - srcs = glob(["resources/**/*"]), - strip_prefix = "resources" -) +load("@rules_jvm//:jvm.bzl", "jvm_library") jvm_library( name = "rendering", module_name = "intellij.platform.icons.api.rendering", visibility = ["//visibility:public"], srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), - resources = [":rendering_resources"], - kotlinc_opts = ":custom_rendering", deps = [ "@lib//:kotlin-stdlib", "//libraries/kotlinx/coroutines/core", diff --git a/platform/icons-api/rendering/intellij.platform.icons.api.rendering.iml b/platform/icons-api/rendering/intellij.platform.icons.api.rendering.iml index fcb79ee11077c..8205298f71adb 100644 --- a/platform/icons-api/rendering/intellij.platform.icons.api.rendering.iml +++ b/platform/icons-api/rendering/intellij.platform.icons.api.rendering.iml @@ -2,15 +2,15 @@ - + - - - - + + + @@ -19,7 +19,6 @@ - diff --git a/platform/icons-api/rendering/resources/intellij.platform.icons.api.rendering.xml b/platform/icons-api/rendering/resources/intellij.platform.icons.api.rendering.xml deleted file mode 100644 index 4bff8c44a8f8f..0000000000000 --- a/platform/icons-api/rendering/resources/intellij.platform.icons.api.rendering.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/platform/icons-api/resources/intellij.platform.icons.api.xml b/platform/icons-api/resources/intellij.platform.icons.api.xml deleted file mode 100644 index ff776b84600f0..0000000000000 --- a/platform/icons-api/resources/intellij.platform.icons.api.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/platform/icons-api/src/org/jetbrains/icons/DeferredIcon.kt b/platform/icons-api/src/org/jetbrains/icons/DeferredIcon.kt index c7af7b609b558..25c62bdcbe6bb 100644 --- a/platform/icons-api/src/org/jetbrains/icons/DeferredIcon.kt +++ b/platform/icons-api/src/org/jetbrains/icons/DeferredIcon.kt @@ -3,18 +3,16 @@ package org.jetbrains.icons import org.jetbrains.annotations.ApiStatus +/** + * Deferred Icon takes time to resolve; therefore, it is postponed to be resolved later. + * Placeholder icon can be set to provide a visual representation while the actual icon is being resolved. + * Unlike the older API, to force evaluation or get the resolved icon, IconManager should be used. + * + * @see IconManager.deferredIcon + * @see IconManager.forceEvaluation + */ @ApiStatus.Experimental interface DeferredIcon: Icon { - val isDone: Boolean + val id: IconIdentifier val placeholder: Icon? -} - -@ApiStatus.Experimental -interface AsyncDeferredIcon: DeferredIcon { - suspend fun resolveInPlaceAsync(): Icon -} - -@ApiStatus.Experimental -interface SyncDeferredIcon: DeferredIcon { - fun resolveInPlace(): Icon -} +} \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/IconIdentifier.kt b/platform/icons-api/src/org/jetbrains/icons/IconIdentifier.kt new file mode 100644 index 0000000000000..8e992085b8e47 --- /dev/null +++ b/platform/icons-api/src/org/jetbrains/icons/IconIdentifier.kt @@ -0,0 +1,4 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons + +interface IconIdentifier \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/IconManager.kt b/platform/icons-api/src/org/jetbrains/icons/IconManager.kt index 8f368576b83e8..95fb7d7764f52 100644 --- a/platform/icons-api/src/org/jetbrains/icons/IconManager.kt +++ b/platform/icons-api/src/org/jetbrains/icons/IconManager.kt @@ -18,12 +18,9 @@ interface IconManager { /** * @see org.jetbrains.icons.deferredIcon */ - fun deferredIcon(placeholder: Icon?, identifier: String? = null, preventClashes: Boolean = true, evaluator: (String) -> Icon?): Icon + fun deferredIcon(placeholder: Icon?, identifier: String? = null, classLoader: ClassLoader? = null, evaluator: suspend () -> Icon): Icon - /** - * @see org.jetbrains.icons.deferredIconAsync - */ - fun deferredIconAsync(placeholder: Icon?, identifier: String? = null, preventClashes: Boolean = true, evaluator: suspend (String) -> Icon?): Icon + suspend fun forceEvaluation(icon: DeferredIcon): Icon /** * Converts specific Icon to swing Icon. @@ -79,19 +76,13 @@ fun icon(designer: IconDesigner.() -> Unit): Icon = IconManager.getInstance().ic * * To cache such icons and synchronize them over the network, some identifier should be given. * Implementations might try to prefix the identifier with the source pluginId/moduleId to avoid clashes. - * If the identifier should be global, preventClashes can be set to false to disable this. * * If the identifier is not passed, an automatic one is created, however, this will prevent the result * from being cached, as a new one is generated per each deferredIcon() call. + * + * @param classLoader This classLoader might be used to prevent id clashes (prefix with pluginId & moduleId if possible for example) */ -fun deferredIcon(placeholder: Icon?, identifier: String? = null, preventClashes: Boolean = true, evaluator: (String) -> Icon?): Icon = - IconManager.getInstance().deferredIcon(placeholder, identifier, preventClashes, evaluator) - -/** - * Alternative for deferredIcon that accepts suspending functions. - * @see deferredIcon - */ -fun deferredIconAsync(placeholder: Icon?, identifier: String? = null, preventClashes: Boolean = true, evaluator: suspend (String) -> Icon?): Icon = - IconManager.getInstance().deferredIconAsync(placeholder, identifier, preventClashes, evaluator) +fun deferredIcon(placeholder: Icon?, identifier: String? = null, classLoader: ClassLoader? = null, evaluator: suspend () -> Icon): Icon = + IconManager.getInstance().deferredIcon(placeholder, identifier, classLoader, evaluator) fun imageIcon(path: String, classLoader: ClassLoader? = null, modifier: IconModifier = IconModifier): Icon = icon { image(path, classLoader, modifier) } \ No newline at end of file diff --git a/platform/icons-impl/BUILD.bazel b/platform/icons-impl/BUILD.bazel index 5eed739c50422..a32794262b170 100644 --- a/platform/icons-impl/BUILD.bazel +++ b/platform/icons-impl/BUILD.bazel @@ -1,28 +1,11 @@ ### auto-generated section `build intellij.platform.icons.impl` start -load("//build:compiler-options.bzl", "create_kotlinc_options") -load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") - -create_kotlinc_options( - name = "custom_icons-impl", - api_version = "2.2", - language_version = "2.2", - progressive = False, - x_x_language = [] -) - -resourcegroup( - name = "icons-impl_resources", - srcs = glob(["resources/**/*"]), - strip_prefix = "resources" -) +load("@rules_jvm//:jvm.bzl", "jvm_library") jvm_library( name = "icons-impl", module_name = "intellij.platform.icons.impl", visibility = ["//visibility:public"], srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), - resources = [":icons-impl_resources"], - kotlinc_opts = ":custom_icons-impl", deps = [ "@lib//:kotlin-stdlib", "//libraries/kotlinx/coroutines/core", diff --git a/platform/icons-impl/intellij.platform.icons.impl.iml b/platform/icons-impl/intellij.platform.icons.impl.iml index 6e14f9483733e..f8d8181fe53d2 100644 --- a/platform/icons-impl/intellij.platform.icons.impl.iml +++ b/platform/icons-impl/intellij.platform.icons.impl.iml @@ -2,15 +2,15 @@ - + - - - - + + + @@ -25,7 +25,6 @@ - diff --git a/platform/icons-impl/intellij/BUILD.bazel b/platform/icons-impl/intellij/BUILD.bazel index f2b65a9b60416..28039ec663eaa 100644 --- a/platform/icons-impl/intellij/BUILD.bazel +++ b/platform/icons-impl/intellij/BUILD.bazel @@ -1,15 +1,6 @@ ### auto-generated section `build intellij.platform.icons.impl.intellij` start -load("//build:compiler-options.bzl", "create_kotlinc_options") load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") -create_kotlinc_options( - name = "custom_intellij", - api_version = "2.2", - language_version = "2.2", - progressive = False, - x_x_language = [] -) - resourcegroup( name = "intellij_resources", srcs = glob(["resources/**/*"]), @@ -22,7 +13,6 @@ jvm_library( visibility = ["//visibility:public"], srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), resources = [":intellij_resources"], - kotlinc_opts = ":custom_intellij", deps = [ "@lib//:kotlin-stdlib", "//libraries/kotlinx/coroutines/core", @@ -39,6 +29,12 @@ jvm_library( "//platform/core-ui", "//libraries/kotlinx/serialization/core", "@lib//:jetbrains-annotations", + "//platform/util/concurrency", + ], + exports = [ + "//platform/icons-api", + "//platform/icons-api/rendering", + "//platform/icons-impl", ] ) @@ -46,7 +42,6 @@ jvm_library( name = "intellij_test_lib", visibility = ["//visibility:public"], srcs = glob(["test/**/*.kt", "test/**/*.java", "test/**/*.form"], allow_empty = True), - kotlinc_opts = ":custom_intellij", associates = [":intellij"], deps = [ "@lib//:kotlin-stdlib", @@ -71,6 +66,13 @@ jvm_library( "@lib//:assert_j", "//libraries/kotlinx/serialization/core", "@lib//:jetbrains-annotations", + "//platform/util/concurrency", + "//platform/util/concurrency:concurrency_test_lib", + ], + exports = [ + "//platform/icons-api", + "//platform/icons-api/rendering", + "//platform/icons-impl", ] ) ### auto-generated section `build intellij.platform.icons.impl.intellij` end diff --git a/platform/icons-impl/intellij/intellij.platform.icons.impl.intellij.iml b/platform/icons-impl/intellij/intellij.platform.icons.impl.intellij.iml index ad55ff1d0e3d0..63f2b94d88f88 100644 --- a/platform/icons-impl/intellij/intellij.platform.icons.impl.intellij.iml +++ b/platform/icons-impl/intellij/intellij.platform.icons.impl.intellij.iml @@ -2,15 +2,15 @@ - + - - - - + + + @@ -33,9 +33,9 @@ - - - + + + @@ -52,5 +52,6 @@ + \ No newline at end of file diff --git a/platform/icons-impl/intellij/resources/intellij.platform.icons.impl.intellij.xml b/platform/icons-impl/intellij/resources/intellij.platform.icons.impl.intellij.xml index 032a65a1ca438..7dcda8ce09813 100644 --- a/platform/icons-impl/intellij/resources/intellij.platform.icons.impl.intellij.xml +++ b/platform/icons-impl/intellij/resources/intellij.platform.icons.impl.intellij.xml @@ -8,20 +8,26 @@ - - - - + + + + + - - + + + diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJDeferredIconResolverService.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJDeferredIconResolverService.kt new file mode 100644 index 0000000000000..17c9a2180d915 --- /dev/null +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJDeferredIconResolverService.kt @@ -0,0 +1,29 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij + +import com.intellij.ide.PowerSaveMode +import com.intellij.openapi.components.Service +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.icons.DeferredIcon +import org.jetbrains.icons.Icon +import org.jetbrains.icons.IconIdentifier +import org.jetbrains.icons.impl.DefaultDeferredIcon +import org.jetbrains.icons.impl.DeferredIconResolver +import org.jetbrains.icons.impl.DeferredIconResolverService +import java.lang.ref.ReferenceQueue +import java.lang.ref.WeakReference +import java.util.concurrent.ConcurrentHashMap +import kotlin.time.Duration.Companion.seconds + +@ApiStatus.Internal +@Service(Service.Level.APP) +class IntelliJDeferredIconResolverService(scope: CoroutineScope): DeferredIconResolverService(scope) { + override fun scheduleEvaluation(icon: DeferredIcon) { + if (!PowerSaveMode.isEnabled()) { + super.scheduleEvaluation(icon) + } + } +} \ No newline at end of file diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt index b86199bf43504..e7e4b871a5150 100644 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt @@ -1,22 +1,79 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.impl.intellij +import com.intellij.ide.plugins.cl.PluginAwareClassLoader +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.ModalityState +import com.intellij.openapi.application.UI +import com.intellij.openapi.application.asContextElement +import com.intellij.openapi.components.service +import com.intellij.util.messages.Topic +import com.intellij.util.messages.Topic.BroadcastDirection +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import kotlinx.serialization.modules.SerializersModuleBuilder +import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.Icon +import org.jetbrains.icons.IconIdentifier import org.jetbrains.icons.ImageResourceLocation import org.jetbrains.icons.design.IconDesigner +import org.jetbrains.icons.impl.DefaultDeferredIcon import org.jetbrains.icons.impl.DefaultIconManager +import org.jetbrains.icons.impl.DeferredIconResolver import org.jetbrains.icons.impl.intellij.custom.CustomIconLayerRegistration import org.jetbrains.icons.impl.intellij.custom.CustomLegacyIconSerializer import org.jetbrains.icons.impl.intellij.design.IntelliJIconDesigner +import org.jetbrains.icons.impl.intellij.rendering.SwingIcon +import java.lang.ref.WeakReference +import kotlin.getValue class IntelliJIconManager : DefaultIconManager() { + override val resolverService: IntelliJDeferredIconResolverService by lazy { + service() + } + + override fun generateDeferredIconIdentifier(id: String?, classLoader: ClassLoader?): IconIdentifier { + if (classLoader != null) { + val (pluginId, moduleId) = getPluginAndModuleId(classLoader) + return ModuleIconIdentifier(pluginId, moduleId, super.generateDeferredIconIdentifier(id, classLoader)) + } + return super.generateDeferredIconIdentifier(id, classLoader) + } + + override fun createDeferredIconResolver( + id: IconIdentifier, + ref: WeakReference, + evaluator: (suspend () -> Icon)? + ): DeferredIconResolver { + if (evaluator == null) { + throw NotImplementedError("Remote Icon evaluation is not supported") + } + else { + return super.createDeferredIconResolver(id, ref, evaluator) + } + } + override fun icon(designer: IconDesigner.() -> Unit): Icon { val ijIconDesigner = IntelliJIconDesigner() ijIconDesigner.designer() return ijIconDesigner.build() } + override fun toSwingIcon(icon: Icon): javax.swing.Icon { + return SwingIcon(icon) + } + + override fun markDeferredIconUnused(id: IconIdentifier) { + // TODO delete unused deferred icons + } + + override suspend fun sendDeferredNotifications(id: IconIdentifier, result: Icon) { + val deferredIconListener = ApplicationManager.getApplication().messageBus.syncPublisher(DeferredIconListener.TOPIC) + withContext(Dispatchers.UI + ModalityState.any().asContextElement()) { + deferredIconListener.evaluated(id, result) + } + } + override fun SerializersModuleBuilder.buildCustomSerializers() { CustomLegacyIconSerializer.registerSerializersTo(this) CustomIconLayerRegistration.registerSerializersTo(this) @@ -25,5 +82,32 @@ class IntelliJIconManager : DefaultIconManager() { ModuleImageResourceLocation::class, ModuleImageResourceLocation.serializer() ) + polymorphic( + IconIdentifier::class, + ModuleIconIdentifier::class, + ModuleIconIdentifier.serializer() + ) + } + + companion object { + internal fun getPluginAndModuleId(classLoader: ClassLoader): Pair { + if (classLoader is PluginAwareClassLoader) { + return classLoader.pluginId.idString to classLoader.moduleId + } + else { + return "com.intellij" to null + } + } + } +} + +@ApiStatus.Internal +interface DeferredIconListener { + fun evaluated(id: IconIdentifier, result: Icon) + + companion object { + @JvmField + @Topic.AppLevel + val TOPIC: Topic = Topic(DeferredIconListener::class.java, BroadcastDirection.NONE) } } \ No newline at end of file diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/ModuleIconIdentifier.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/ModuleIconIdentifier.kt new file mode 100644 index 0000000000000..be17a3bd05756 --- /dev/null +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/ModuleIconIdentifier.kt @@ -0,0 +1,36 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij + +import kotlinx.serialization.Serializable +import org.jetbrains.icons.IconIdentifier + +@Serializable +class ModuleIconIdentifier( + val pluginId: String, + val moduleId: String?, + val uniqueId: IconIdentifier +): IconIdentifier { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ModuleIconIdentifier + + if (pluginId != other.pluginId) return false + if (moduleId != other.moduleId) return false + if (uniqueId != other.uniqueId) return false + + return true + } + + override fun hashCode(): Int { + var result = pluginId.hashCode() + result = 31 * result + moduleId.hashCode() + result = 31 * result + uniqueId.hashCode() + return result + } + + override fun toString(): String { + return "ModuleIconIdentifier(pluginId='$pluginId', moduleId='$moduleId', uniqueId='$uniqueId')" + } +} diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/ModuleImageResourceLocation.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/ModuleImageResourceLocation.kt index 8ddfbaa70e3a1..92fb45f3f6cca 100644 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/ModuleImageResourceLocation.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/ModuleImageResourceLocation.kt @@ -37,18 +37,10 @@ class ModuleImageResourceLocation( companion object { fun fromClassLoader(path: String, classLoader: ClassLoader): ImageResourceLocation { - val (pluginId, moduleId) = getPluginAndModuleId(classLoader) + val (pluginId, moduleId) = IntelliJIconManager.getPluginAndModuleId(classLoader) return ModuleImageResourceLocation(path, pluginId, moduleId) } - private fun getPluginAndModuleId(classLoader: ClassLoader): Pair { - if (classLoader is PluginAwareClassLoader) { - return classLoader.pluginId.idString to classLoader.moduleId - } - else { - return "com.intellij" to null - } - } } } diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomIconLayerRegistration.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomIconLayerRegistration.kt index 06f55e831fddd..65e4fa4808aa6 100644 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomIconLayerRegistration.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomIconLayerRegistration.kt @@ -11,6 +11,6 @@ abstract class CustomIconLayerRegistration( @ApiStatus.Internal companion object: CustomSerializableRegistration.Companion>( IconLayer ::class, - "com.intellij.customIconLayer" + "com.intellij.icons.customLayer" ) } \ No newline at end of file diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomLegacyIconSerializer.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomLegacyIconSerializer.kt index 1783576c9b455..2d052b64ed862 100644 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomLegacyIconSerializer.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/custom/CustomLegacyIconSerializer.kt @@ -11,6 +11,6 @@ abstract class CustomLegacyIconSerializer( @ApiStatus.Internal companion object: CustomSerializableRegistration.Companion>( Icon::class, - "com.intellij.customLegacyIconSerializer" + "com.intellij.icons.customLegacyIconSerializer" ) } diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/Convertors.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/Convertors.kt index cf2f8c77d7330..c3c8faa66a7fd 100644 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/Convertors.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/Convertors.kt @@ -8,7 +8,6 @@ import org.jetbrains.icons.filters.ColorFilter import org.jetbrains.icons.filters.TintColorFilter import org.jetbrains.icons.patchers.SvgPatchOperation import org.jetbrains.icons.patchers.SvgPatcher -import org.jetbrains.icons.impl.rendering.toAwtColor import java.awt.Color import java.awt.image.RGBImageFilter diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IconUpdateService.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IconUpdateService.kt index c7e74247ac8b5..420877aa3ccb2 100644 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IconUpdateService.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IconUpdateService.kt @@ -8,7 +8,6 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch import org.jetbrains.annotations.ApiStatus -import org.jetbrains.annotations.ApiStatus @Service(Service.Level.APP) @ApiStatus.Internal diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJIconRendererManager.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJIconRendererManager.kt index 0f85af9ce6ad0..fa89fe1888725 100644 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJIconRendererManager.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/IntelliJIconRendererManager.kt @@ -4,7 +4,6 @@ package org.jetbrains.icons.impl.intellij.rendering import com.intellij.util.ui.StartupUiUtil import kotlinx.coroutines.CoroutineScope import org.jetbrains.annotations.ApiStatus -import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.impl.intellij.rendering.custom.CustomIconLayerRendererProvider import org.jetbrains.icons.rendering.ImageModifiers import org.jetbrains.icons.rendering.MutableIconUpdateFlow @@ -16,7 +15,6 @@ import org.jetbrains.icons.impl.rendering.DefaultImageModifiers import org.jetbrains.icons.impl.rendering.layers.IconLayerRenderer @Suppress("UNCHECKED_CAST") -@OptIn(InternalIconsApi::class, ExperimentalIconsApi::class) class IntelliJIconRendererManager: DefaultIconRendererManager() { override fun createRenderer(layer: IconLayer, renderingContext: RenderingContext): IconLayerRenderer { val defaultRenderer = createRendererOrNull(layer, renderingContext) diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingIcon.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIcon.kt similarity index 97% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingIcon.kt rename to platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIcon.kt index 5edfe03434b11..98d0ef47e4cd5 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingIcon.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIcon.kt @@ -1,5 +1,5 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.jetbrains.icons.impl.rendering +package org.jetbrains.icons.impl.intellij.rendering import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.Icon diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRenderer.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRenderer.kt index 6adf29c48caa6..d5b744dc4ad41 100644 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRenderer.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIconLayerRenderer.kt @@ -1,11 +1,18 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.impl.intellij.rendering +import com.intellij.ide.PowerSaveMode import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.ModalityState +import com.intellij.openapi.application.UI +import com.intellij.openapi.application.asContextElement import com.intellij.ui.DeferredIcon import com.intellij.ui.DeferredIconListener import com.intellij.util.messages.impl.subscribeAsFlow +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withContext import org.jetbrains.icons.impl.intellij.rendering.custom.CustomIconLayerRendererProvider import org.jetbrains.icons.impl.rendering.DefaultImageResourceProvider import org.jetbrains.icons.rendering.Dimensions @@ -18,8 +25,11 @@ import org.jetbrains.icons.rendering.ScalingContext import org.jetbrains.icons.impl.rendering.layers.applyTo import org.jetbrains.icons.impl.rendering.layers.generateImageModifiers import org.jetbrains.icons.layers.IconLayer +import org.jetbrains.icons.rendering.PaintingApi import org.jetbrains.icons.swing.SwingIconLayer +import java.util.concurrent.atomic.AtomicBoolean import javax.swing.Icon +import kotlin.coroutines.resume class SwingIconLayerRendererProvider: CustomIconLayerRendererProvider { override fun handles(layer: IconLayer): Boolean { @@ -32,7 +42,6 @@ class SwingIconLayerRendererProvider: CustomIconLayerRendererProvider { swingLayer, renderingContext ) - renderer.launchEventBridge() return renderer } } @@ -41,21 +50,29 @@ class SwingIconLayerRenderer( override val layer: SwingIconLayer, override val renderingContext: RenderingContext ): BaseImageIconLayerRenderer() { + private val deferredIcon = layer.legacyIcon as? DeferredIcon? + private var isDone = deferredIcon?.isDone ?: false override var image: ImageResource = createImageResource() + private val isPending = AtomicBoolean(false) - fun launchEventBridge() { - val icon = layer.legacyIcon - if (layer.legacyIcon is DeferredIcon) { - val flow = ApplicationManager.getApplication().messageBus.subscribeAsFlow(DeferredIconListener.TOPIC) { - object : DeferredIconListener { - override fun evaluated(deferred: DeferredIcon, result: Icon) { - if (deferred != icon) return - trySend(result) + override fun render(api: PaintingApi) { + checkForUpdatesIfNeeded() + super.render(api) + } + + private fun checkForUpdatesIfNeeded() { + if (deferredIcon != null && !PowerSaveMode.isEnabled() && !isDone) { + if (!isPending.getAndSet(true)) { + if (!deferredIcon.isDone) { + IconUpdateService.getInstance().scope.launch { + withContext(Dispatchers.UI + ModalityState.any().asContextElement()) { + suspendUntilDeferredIconIsDone() + isDone = true + image = createImageResource() + } } - } - } - IconUpdateService.getInstance().scope.launch { - flow.collect { + } else { + isDone = true image = createImageResource() } } @@ -71,4 +88,23 @@ class SwingIconLayerRenderer( override fun calculateExpectedDimensions(scaling: ScalingContext): Dimensions { return Dimensions(scaling.applyTo(image.width) ?: 16, scaling.applyTo(image.height) ?: 16) } + + private suspend fun suspendUntilDeferredIconIsDone() { + val connection = ApplicationManager.getApplication().messageBus.simpleConnect() + try { + suspendCancellableCoroutine { continuation -> + val listener = object : DeferredIconListener { + override fun evaluated(deferred: DeferredIcon, result: Icon) { + if (deferred === this@SwingIconLayerRenderer.deferredIcon) { + continuation.resume(Unit) + } + } + } + connection.subscribe(DeferredIconListener.TOPIC, listener) + } + } + finally { + connection.disconnect() + } + } } \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingPaintingApi.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingPaintingApi.kt similarity index 97% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingPaintingApi.kt rename to platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingPaintingApi.kt index 18384160e5567..c02cd5da1dff0 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/SwingPaintingApi.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingPaintingApi.kt @@ -1,8 +1,9 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.jetbrains.icons.impl.rendering +package org.jetbrains.icons.impl.intellij.rendering import org.jetbrains.icons.design.Color import org.jetbrains.icons.filters.ColorFilter +import org.jetbrains.icons.impl.intellij.rendering.images.awtImage import org.jetbrains.icons.rendering.BitmapImageResource import org.jetbrains.icons.rendering.Bounds import org.jetbrains.icons.rendering.DrawMode @@ -20,7 +21,6 @@ import java.awt.RenderingHints import java.awt.Shape import java.awt.geom.Ellipse2D import java.awt.geom.Rectangle2D -import kotlin.text.toInt class SwingPaintingApi( val c: Component?, @@ -48,7 +48,7 @@ class SwingPaintingApi( override fun getUsedBounds(): Bounds = bounds override fun withCustomContext(bounds: Bounds, overrideColorFilter: ColorFilter?): PaintingApi { - return _root_ide_package_.org.jetbrains.icons.impl.rendering.SwingPaintingApi( + return SwingPaintingApi( c, g, x, diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/custom/CustomIconLayerRendererProvider.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/custom/CustomIconLayerRendererProvider.kt index f5642ce7e1519..49f3f995dc067 100644 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/custom/CustomIconLayerRendererProvider.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/custom/CustomIconLayerRendererProvider.kt @@ -20,6 +20,6 @@ interface CustomIconLayerRendererProvider { return null } - val EP_NAME: ExtensionPointName = ExtensionPointName("com.intellij.icons.customIconLayerRendererProvider") + val EP_NAME: ExtensionPointName = ExtensionPointName("com.intellij.icons.customLayerRendererProvider") } } \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/AwtImageResource.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/images/AwtImageResource.kt similarity index 95% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/AwtImageResource.kt rename to platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/images/AwtImageResource.kt index 1cf7e1ca98a31..24b4bc5b50b32 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/AwtImageResource.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/images/AwtImageResource.kt @@ -1,6 +1,7 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.jetbrains.icons.impl.rendering +package org.jetbrains.icons.impl.intellij.rendering.images +import org.jetbrains.icons.impl.rendering.CachedGPUImageResourceHolder import org.jetbrains.icons.rendering.BitmapImageResource import org.jetbrains.icons.rendering.lowlevel.GPUImageResourceHolder import java.awt.Image diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/images/IntelliJImageResource.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/images/IntelliJImageResource.kt index ffb5aea7a2aa3..6f0c39e21990f 100644 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/images/IntelliJImageResource.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/images/IntelliJImageResource.kt @@ -4,7 +4,6 @@ package org.jetbrains.icons.impl.intellij.rendering.images import com.intellij.ui.scale.ScaleContext import com.intellij.ui.scale.ScaleType import com.intellij.util.JBHiDPIScaledImage -import org.jetbrains.icons.impl.rendering.AwtImageResource import org.jetbrains.icons.impl.rendering.CachedGPUImageResourceHolder import org.jetbrains.icons.rendering.BitmapImageResource import org.jetbrains.icons.rendering.Bounds diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/toAwtColor.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/toAwtColor.kt similarity index 67% rename from platform/icons-impl/src/org/jetbrains/icons/impl/rendering/toAwtColor.kt rename to platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/toAwtColor.kt index d06d9bc5dae72..8ed0475bb784c 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/toAwtColor.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/toAwtColor.kt @@ -1,9 +1,10 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.jetbrains.icons.impl.rendering +package org.jetbrains.icons.impl.intellij.rendering +import org.jetbrains.icons.design.Color import org.jetbrains.icons.design.RGBA -fun org.jetbrains.icons.design.Color.toAwtColor(): java.awt.Color { +fun Color.toAwtColor(): java.awt.Color { @Suppress("UseJBColor") when (this) { is RGBA -> return java.awt.Color(red, green, blue, alpha) diff --git a/platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DynamicIconTest.kt b/platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DynamicIconTest.kt index 326b68c9c85a5..a08f7a3308259 100644 --- a/platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DynamicIconTest.kt +++ b/platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DynamicIconTest.kt @@ -27,7 +27,6 @@ import org.junit.jupiter.api.Test @OptIn(DelicateCoroutinesApi::class) @TestApplication class DynamicIconTest { - @OptIn(ExperimentalIconsApi::class) @Test fun `should be properly updated after serialization and deserialization`() { diff --git a/platform/icons-impl/resources/intellij.platform.icons.impl.xml b/platform/icons-impl/resources/intellij.platform.icons.impl.xml deleted file mode 100644 index eb960f7b3edea..0000000000000 --- a/platform/icons-impl/resources/intellij.platform.icons.impl.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/platform/icons-impl/src/StringIconIdentifier.kt b/platform/icons-impl/src/StringIconIdentifier.kt new file mode 100644 index 0000000000000..e0a2537f2ec7a --- /dev/null +++ b/platform/icons-impl/src/StringIconIdentifier.kt @@ -0,0 +1,26 @@ +import kotlinx.serialization.Serializable +import org.jetbrains.icons.IconIdentifier + +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. + +@Serializable +class StringIconIdentifier( + val value: String +): IconIdentifier { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as StringIconIdentifier + + return value == other.value + } + + override fun hashCode(): Int { + return value.hashCode() + } + + override fun toString(): String { + return value + } +} diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultDeferredIcon.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultDeferredIcon.kt index 9541576637436..9b4bd57c77873 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultDeferredIcon.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultDeferredIcon.kt @@ -1,16 +1,75 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.impl +import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.serializer +import org.jetbrains.annotations.ApiStatus import org.jetbrains.icons.DeferredIcon import org.jetbrains.icons.Icon +import org.jetbrains.icons.IconIdentifier +import java.lang.ref.WeakReference @Serializable open class DefaultDeferredIcon( - internal val id: String?, - override val placeholder: Icon?, + override val id: IconIdentifier, + /** + * Placeholder that is renderer when the icon is not resolved yet. + * Keep in mind that implementation might change this to null when + * the icon is resolved to reduce memory footprint when serialized. + */ + override var placeholder: Icon? ): DeferredIcon { - override val isDone: Boolean = false - internal val currentIcon: Icon? = placeholder + @Transient + private val listeners = mutableListOf>() + @ApiStatus.Internal + fun addDoneListener(listener: DeferredIconEventHandler) { + listeners.add(WeakReference(listener)) + } + + @ApiStatus.Internal + fun markDone(resolvedIcon: Icon) { + this.placeholder = null + for (listener in listeners) { + listener.get()?.whenDone(this, resolvedIcon) + } + listeners.clear() + } + +} + +class DefaultDeferredIconSerializer( + private val manager: DefaultIconManager +) : KSerializer { + private val delegate = DefaultDeferredIcon.serializer() + + override val descriptor: SerialDescriptor = delegate.descriptor + + override fun serialize(encoder: Encoder, value: DefaultDeferredIcon) { + delegate.serialize(encoder, value) + } + + override fun deserialize(decoder: Decoder): DefaultDeferredIcon { + val result = delegate.deserialize(decoder) + return manager.registerDeserializedDeferredIcon(result) + } } + +interface DeferredIconEventHandler { + fun whenDone(deferredIcon: DeferredIcon, resolvedIcon: Icon) +} + +/** + * Responsible for resolving deferred icons, + * and also synchronization between instances and backend/frontend. + */ +interface DeferredIconResolver { + val id: IconIdentifier + val deferredIcon: WeakReference + suspend fun resolve(): Icon +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultIconManager.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultIconManager.kt index 0cafe56e8d525..db1c90a1d107c 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultIconManager.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultIconManager.kt @@ -1,7 +1,9 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.impl +import StringIconIdentifier import kotlinx.serialization.KSerializer +import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.buildClassSerialDescriptor import kotlinx.serialization.descriptors.serialDescriptor @@ -14,6 +16,7 @@ import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.SerializersModuleBuilder import org.jetbrains.icons.DeferredIcon import org.jetbrains.icons.Icon +import org.jetbrains.icons.IconIdentifier import org.jetbrains.icons.IconManager import org.jetbrains.icons.impl.layers.AnimatedIconLayer import org.jetbrains.icons.impl.layers.IconIconLayer @@ -21,30 +24,74 @@ import org.jetbrains.icons.impl.layers.ImageIconLayer import org.jetbrains.icons.impl.layers.LayoutIconLayer import org.jetbrains.icons.impl.layers.ShapeIconLayer import org.jetbrains.icons.modifiers.IconModifier +import java.lang.ref.WeakReference +import java.util.concurrent.atomic.AtomicInteger abstract class DefaultIconManager: IconManager { - override fun deferredIcon(placeholder: Icon?, identifier: String?, preventClashes: Boolean, evaluator: (String) -> Icon?): Icon { - return DefaultDeferredIcon(identifier, placeholder) + protected abstract val resolverService: DeferredIconResolverService + + private val deferredIconDeserializer by lazy { + DefaultDeferredIconSerializer(this) + } + + override fun deferredIcon(placeholder: Icon?, identifier: String?, classLoader: ClassLoader?, evaluator: suspend () -> Icon): Icon { + return resolverService.getOrCreateDeferredIcon( + generateDeferredIconIdentifier(identifier, classLoader), + placeholder + ) { id, ref -> + createDeferredIconResolver(id, ref, evaluator) + } + } + + internal fun registerDeserializedDeferredIcon(icon: DefaultDeferredIcon): DefaultDeferredIcon { + return resolverService.register(icon) { id, ref -> + createDeferredIconResolver(id, ref, null) + } + } + + protected open fun generateDeferredIconIdentifier(id: String?, classLoader: ClassLoader? = null): IconIdentifier { + if (id != null) return StringIconIdentifier(id) + return StringIconIdentifier("dynamicIcon_" + dynamicIconNextId.getAndIncrement().toString()) } - override fun deferredIconAsync(placeholder: Icon?, identifier: String?, preventClashes: Boolean, evaluator: suspend (String) -> Icon?): Icon { - return DefaultDeferredIcon(identifier, placeholder) + override suspend fun forceEvaluation(icon: DeferredIcon): Icon { + return resolverService.forceEvaluation(icon) } - protected open fun SerializersModuleBuilder.buildCustomSerializers() { - // Register nothing in default implementation + fun scheduleEvaluation(icon: DeferredIcon) { + resolverService.scheduleEvaluation(icon) } + protected open fun createDeferredIconResolver( + id: IconIdentifier, + ref: WeakReference, + evaluator: (suspend () -> Icon)?, + ): DeferredIconResolver { + if (evaluator == null) error("Evaluator is not specified for icon $id") + return InPlaceDeferredIconResolver(resolverService, id, ref, evaluator) + } + + open fun SerializersModuleBuilder.buildCustomSerializers() { + // Add nothing by default + } + abstract suspend fun sendDeferredNotifications(id: IconIdentifier, result: Icon) + abstract fun markDeferredIconUnused(id: IconIdentifier) + override fun getSerializersModule(): SerializersModule { return SerializersModule { polymorphic(Icon::class, DefaultLayeredIcon::class, DefaultLayeredIcon.serializer()) - polymorphic(Icon::class, DefaultDeferredIcon::class, DefaultDeferredIcon.serializer()) - polymorphic(DeferredIcon::class, DefaultDeferredIcon::class, DefaultDeferredIcon.serializer()) + polymorphic(Icon::class, DefaultDeferredIcon::class, deferredIconDeserializer) + polymorphic(DeferredIcon::class, DefaultDeferredIcon::class, deferredIconDeserializer) polymorphic( IconModifier::class, IconModifier.Companion::class, IconModifierConstSerializer ) + polymorphic( + IconIdentifier::class, + StringIconIdentifier::class, + StringIconIdentifier.serializer() + ) iconLayer(AnimatedIconLayer::class) iconLayer(IconIconLayer::class) @@ -60,7 +107,13 @@ abstract class DefaultIconManager: IconManager { error("Swing Icons are not supported.") } - private var dynamicIconNextId = 0 + companion object { + fun getDefaultManagerInstance(): DefaultIconManager { + return IconManager.getInstance() as? DefaultIconManager ?: error("IconManager is not DefaultIconManager.") + } + + private val dynamicIconNextId = AtomicInteger() + } } private object IconModifierConstSerializer : KSerializer { diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/DeferredIconResolverService.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/DeferredIconResolverService.kt new file mode 100644 index 0000000000000..962c2f18dcb64 --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/DeferredIconResolverService.kt @@ -0,0 +1,82 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.icons.DeferredIcon +import org.jetbrains.icons.Icon +import org.jetbrains.icons.IconIdentifier +import java.lang.ref.ReferenceQueue +import java.lang.ref.WeakReference +import java.util.concurrent.ConcurrentHashMap +import kotlin.time.Duration.Companion.seconds + +@ApiStatus.Internal +open class DeferredIconResolverService( + protected val scope: CoroutineScope +) { + protected val iconReferenceQueue = ReferenceQueue() + protected val resolvers = ConcurrentHashMap() + + init { + scope.launch { + while (true) { + delay(5.seconds) + cleanUnusedIcons() + } + } + } + + open fun getOrCreateDeferredIcon( + identifier: IconIdentifier, + placeholder: Icon?, + resolverBuilder: (IconIdentifier, WeakReference) -> DeferredIconResolver + ): Icon { + val resolver = resolvers.getOrPut(identifier) { + val icon = DefaultDeferredIcon(identifier, placeholder) + resolverBuilder(icon.id, IdentifiedDeferredIconWeakReference(icon, iconReferenceQueue)) + } + return resolver?.deferredIcon?.get() ?: DefaultDeferredIcon(identifier, placeholder) + } + + open fun register( + icon: DefaultDeferredIcon, + resolverBuilder: (IconIdentifier, WeakReference) -> DeferredIconResolver + ): DefaultDeferredIcon { + return resolvers.getOrPut(icon.id) { + resolverBuilder(icon.id, IdentifiedDeferredIconWeakReference(icon, iconReferenceQueue)) + }?.deferredIcon?.get() ?: icon + } + + open fun scheduleEvaluation(icon: DeferredIcon) { + scope.launch { + forceEvaluation(icon) + } + } + + open suspend fun forceEvaluation(icon: DeferredIcon): Icon { + val resolver = resolvers[icon.id] ?: error("Cannot find resolver for icon: $icon") + return resolver.resolve() + } + + open fun cleanIcon(id: IconIdentifier) { + resolvers.remove(id) + } + + protected open fun cleanUnusedIcons() { + while (true) { + val reference = iconReferenceQueue.poll() ?: break + val id = (reference as IdentifiedDeferredIconWeakReference).id ?: continue + DefaultIconManager.getDefaultManagerInstance().markDeferredIconUnused(id) + } + } + + protected open class IdentifiedDeferredIconWeakReference( + instance: DefaultDeferredIcon, + queue: ReferenceQueue, + ): WeakReference(instance, queue) { + val id = instance.id + } +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/InPlaceDeferredIconResolver.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/InPlaceDeferredIconResolver.kt new file mode 100644 index 0000000000000..7cdcd0237c28b --- /dev/null +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/InPlaceDeferredIconResolver.kt @@ -0,0 +1,36 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl + +import kotlinx.coroutines.CompletableDeferred +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.icons.Icon +import org.jetbrains.icons.IconIdentifier +import java.lang.ref.WeakReference +import java.util.concurrent.atomic.AtomicBoolean + +@ApiStatus.Internal +class InPlaceDeferredIconResolver( + val service: DeferredIconResolverService, + override val id: IconIdentifier, + override val deferredIcon: WeakReference, + val evaluator: suspend () -> Icon +): DeferredIconResolver { + var resolvedIcon: Icon? = null + private val deferredValue = CompletableDeferred() + private val isPending = AtomicBoolean(false) + + override suspend fun resolve(): Icon { + val resolved = resolvedIcon + if (resolved != null) return resolved + if (!isPending.getAndSet(true)) { + val result = evaluator() + deferredValue.complete(result) + resolvedIcon = result + deferredIcon.get()?.markDone(result) + DefaultIconManager.getDefaultManagerInstance().sendDeferredNotifications(id, result) + return result + } + return deferredValue.await() + } +} + diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultDeferredIconRenderer.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultDeferredIconRenderer.kt index 04c89e4fbc0ad..19aec75288f69 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultDeferredIconRenderer.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultDeferredIconRenderer.kt @@ -2,7 +2,12 @@ package org.jetbrains.icons.impl.rendering import org.jetbrains.annotations.ApiStatus +import org.jetbrains.icons.DeferredIcon +import org.jetbrains.icons.Icon +import org.jetbrains.icons.IconManager import org.jetbrains.icons.impl.DefaultDeferredIcon +import org.jetbrains.icons.impl.DefaultIconManager +import org.jetbrains.icons.impl.DeferredIconEventHandler import org.jetbrains.icons.rendering.Dimensions import org.jetbrains.icons.rendering.IconRenderer import org.jetbrains.icons.rendering.LoadingStrategy @@ -11,17 +16,29 @@ import org.jetbrains.icons.rendering.RenderingContext import org.jetbrains.icons.rendering.ScalingContext import org.jetbrains.icons.rendering.createRenderer -// TODO Implement actual resolving & data transfer internal class DefaultDeferredIconRenderer( override val icon: DefaultDeferredIcon, val renderingContext: RenderingContext, - loadingStrategy: LoadingStrategy -): IconRenderer { - private var currentIcon = icon.currentIcon - private var renderer = currentIcon?.createRenderer(renderingContext, loadingStrategy) + val loadingStrategy: LoadingStrategy +): IconRenderer, DeferredIconEventHandler { + private var isDone = false + private var renderer = icon.placeholder?.createRenderer(renderingContext, loadingStrategy) + + override fun whenDone(deferredIcon: DeferredIcon, resolvedIcon: Icon) { + val oldRenderer = renderer + val strategy = if (oldRenderer != null) { + LoadingStrategy.RenderPlaceholder(oldRenderer) + } else loadingStrategy + renderer = resolvedIcon.createRenderer(renderingContext, strategy) + isDone = true + renderingContext.updateFlow.triggerUpdate() + } @ApiStatus.Internal override fun render(api: PaintingApi) { + if (!isDone) { + DefaultIconManager.getDefaultManagerInstance().scheduleEvaluation(icon) + } renderer?.render(api) } diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultIconRendererManager.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultIconRendererManager.kt index 47837c20c7949..cec0da3d961cb 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultIconRendererManager.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/DefaultIconRendererManager.kt @@ -34,11 +34,17 @@ abstract class DefaultIconRendererManager: IconRendererManager, IconLayerManager protected fun createRendererOrNull(icon: Icon, context: RenderingContext, loadingStrategy: LoadingStrategy): IconRenderer? { return when (icon) { is DefaultLayeredIcon -> DefaultIconRenderer(icon, context, loadingStrategy) - is DefaultDeferredIcon -> DefaultDeferredIconRenderer(icon, context, loadingStrategy) + is DefaultDeferredIcon -> createDeferredIconRenderer(icon, context, loadingStrategy) else -> null } } + private fun createDeferredIconRenderer(icon: DefaultDeferredIcon, context: RenderingContext, loadingStrategy: LoadingStrategy): IconRenderer { + val renderer = DefaultDeferredIconRenderer(icon, context, loadingStrategy) + icon.addDoneListener(renderer) + return renderer + } + override fun createRenderer(layer: IconLayer, renderingContext: RenderingContext): IconLayerRenderer { return createRendererOrNull(layer, renderingContext) ?: error("Unsupported icon layer type: $layer") } diff --git a/platform/ide-core/resources/META-INF/intellij.moduleSets.core.platform.xml b/platform/ide-core/resources/META-INF/intellij.moduleSets.core.platform.xml index 64750edee977d..7e4afe5b8938e 100644 --- a/platform/ide-core/resources/META-INF/intellij.moduleSets.core.platform.xml +++ b/platform/ide-core/resources/META-INF/intellij.moduleSets.core.platform.xml @@ -130,10 +130,6 @@ - - - - diff --git a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconManager.kt b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconManager.kt index 3b2054a08459e..808972654342c 100644 --- a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconManager.kt +++ b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/icon/StandaloneIconManager.kt @@ -1,14 +1,29 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.jewel.intui.standalone.icon +import kotlinx.coroutines.CoroutineScope import org.jetbrains.icons.Icon +import org.jetbrains.icons.IconIdentifier import org.jetbrains.icons.design.IconDesigner import org.jetbrains.icons.impl.DefaultIconManager +import org.jetbrains.icons.impl.DeferredIconResolverService + +internal class StandaloneIconManager(scope: CoroutineScope) : DefaultIconManager() { + override val resolverService: DeferredIconResolverService = DeferredIconResolverService(scope) + + override suspend fun sendDeferredNotifications(id: IconIdentifier, result: Icon) { + // Do nothing + } + + override fun markDeferredIconUnused(id: IconIdentifier) { + resolverService.cleanIcon(id) + } -internal class StandaloneIconManager : DefaultIconManager() { override fun icon(designer: IconDesigner.() -> Unit): Icon { val iconDesigner = StandaloneIconDesigner() iconDesigner.designer() return iconDesigner.build() } } + +internal class StandaloneDeferredIconResolverService(scope: CoroutineScope) : DeferredIconResolverService(scope) diff --git a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/theme/IntUiTheme.kt b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/theme/IntUiTheme.kt index 8e390b5498221..25a41d4feabf2 100644 --- a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/theme/IntUiTheme.kt +++ b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/theme/IntUiTheme.kt @@ -5,8 +5,10 @@ package org.jetbrains.jewel.intui.standalone.theme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextStyle +import org.jetbrains.icons.IconManager import org.jetbrains.jewel.foundation.DisabledAppearanceValues import org.jetbrains.jewel.foundation.GlobalColors import org.jetbrains.jewel.foundation.GlobalMetrics @@ -20,6 +22,7 @@ import org.jetbrains.jewel.intui.standalone.IntUiMessageResourceResolver import org.jetbrains.jewel.intui.standalone.IntUiTypography import org.jetbrains.jewel.intui.standalone.StandalonePainterHintsProvider import org.jetbrains.jewel.intui.standalone.StandalonePlatformCursorController +import org.jetbrains.jewel.intui.standalone.icon.StandaloneIconManager import org.jetbrains.jewel.intui.standalone.icon.StandaloneNewUiChecker import org.jetbrains.jewel.intui.standalone.menuShortcut.StandaloneMenuItemShortcutHintProvider import org.jetbrains.jewel.intui.standalone.menuShortcut.StandaloneShortcutProvider @@ -1075,6 +1078,8 @@ public fun IntUiTheme( swingCompatMode: Boolean = false, content: @Composable () -> Unit, ) { + val managerScope = rememberCoroutineScope() + IconManager.activate(StandaloneIconManager(managerScope)) BaseJewelTheme(theme, ComponentStyling.default().with(styling), swingCompatMode) { CompositionLocalProvider( LocalPainterHintsProvider provides StandalonePainterHintsProvider(theme), diff --git a/platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.IconManager b/platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.IconManager deleted file mode 100644 index 505761a636497..0000000000000 --- a/platform/jewel/int-ui/int-ui-standalone/src/main/resources/META-INF/services/org.jetbrains.icons.IconManager +++ /dev/null @@ -1 +0,0 @@ -org.jetbrains.jewel.intui.standalone.icon.StandaloneIconManager diff --git a/platform/jewel/int-ui/int-ui-standalone/src/main/resources/intellij.platform.jewel.intUi.standalone.xml b/platform/jewel/int-ui/int-ui-standalone/src/main/resources/intellij.platform.jewel.intUi.standalone.xml index 0204e03d55ea2..ef1eb97a61734 100644 --- a/platform/jewel/int-ui/int-ui-standalone/src/main/resources/intellij.platform.jewel.intUi.standalone.xml +++ b/platform/jewel/int-ui/int-ui-standalone/src/main/resources/intellij.platform.jewel.intUi.standalone.xml @@ -1,9 +1,6 @@ - - - diff --git a/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/components/Icons.kt b/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/components/Icons.kt index ee71ce8680d81..cc85e7e7bd1da 100644 --- a/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/components/Icons.kt +++ b/platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/components/Icons.kt @@ -192,11 +192,11 @@ public fun Icons(modifier: Modifier = Modifier) { modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp), ) { - Image(ShowcaseIcons.jewelLogoKey, contentDescription = "Jewel Logo", modifier = Modifier.size(96.dp)) + Image(ShowcaseIcons.jewelLogo, contentDescription = "Jewel Logo", modifier = Modifier.size(96.dp)) // Note: this currently looks identical to the hint-less image due to JEWEL-971 Image( - iconKey = ShowcaseIcons.jewelLogoKey, + iconKey = ShowcaseIcons.jewelLogo, contentDescription = "Jewel Logo with hint", hints = arrayOf(Stroke(Color.Red)), modifier = Modifier.size(96.dp), diff --git a/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/TitleBarView.kt b/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/TitleBarView.kt index 46ca9bda8235d..6ddb2500679f2 100644 --- a/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/TitleBarView.kt +++ b/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/TitleBarView.kt @@ -79,7 +79,7 @@ internal fun DecoratedWindowScope.TitleBarView() { { Desktop.getDesktop().browse(URI.create(jewelGithubLink)) }, Modifier.size(40.dp).padding(5.dp), ) { - Icon(ShowcaseIcons.gitHubKey, "Github") + Icon(ShowcaseIcons.gitHub, "Github") } } @@ -121,7 +121,7 @@ internal fun DecoratedWindowScope.TitleBarView() { ) IntUiThemes.Dark -> - Icon(ShowcaseIcons.themeDarkKey, contentDescription = "Dark") + Icon(ShowcaseIcons.themeDark, contentDescription = "Dark") IntUiThemes.System -> Icon( diff --git a/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/WelcomeView.kt b/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/WelcomeView.kt index c0ae8828df6e6..327d66f760cc2 100644 --- a/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/WelcomeView.kt +++ b/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/WelcomeView.kt @@ -41,7 +41,7 @@ internal fun WelcomeView() { .semantics { isTraversalGroup = true }, verticalArrangement = Arrangement.spacedBy(24.dp), ) { - Icon(key = ShowcaseIcons.jewelLogoKey, contentDescription = null, modifier = Modifier.size(200.dp)) + Icon(key = ShowcaseIcons.jewelLogo, contentDescription = null, modifier = Modifier.size(200.dp)) Text("Meet Jewel", style = JewelTheme.typography.h1TextStyle) @@ -52,7 +52,7 @@ internal fun WelcomeView() { horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp), ) { - ThemeSelectionChip(IntUiThemes.Dark, "Dark", ShowcaseIcons.themeDarkKey) + ThemeSelectionChip(IntUiThemes.Dark, "Dark", ShowcaseIcons.themeDark) ThemeSelectionChip(IntUiThemes.Light, "Light", ShowcaseIcons.themeLight) diff --git a/platform/jewel/ui/src/main/resources/intellij.platform.jewel.ui.xml b/platform/jewel/ui/src/main/resources/intellij.platform.jewel.ui.xml index cd995c662cfe6..b8e93d174d476 100644 --- a/platform/jewel/ui/src/main/resources/intellij.platform.jewel.ui.xml +++ b/platform/jewel/ui/src/main/resources/intellij.platform.jewel.ui.xml @@ -3,10 +3,6 @@ - - - - diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.ide.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.ide.xml index 40f3a892dd9e3..4c4131e9934e4 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.ide.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.ide.xml @@ -131,10 +131,6 @@ - - - - diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.lang.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.lang.xml index 088997f79ccce..de17a98711e5c 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.lang.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.lang.xml @@ -132,10 +132,6 @@ - - - - @@ -173,8 +169,7 @@ - - + diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.minimal.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.minimal.xml index ecf55f86773c7..65bb49a42b073 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.minimal.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.minimal.xml @@ -133,10 +133,6 @@ - - - - @@ -174,8 +170,7 @@ - - + diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.xml index 9f2c1b1baa1e0..82511b4aba16f 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.xml @@ -134,10 +134,6 @@ - - - - @@ -175,8 +171,7 @@ - - + diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.ide.common.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.ide.common.xml index 86d2537eaaab0..bcd8085885db0 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.ide.common.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.ide.common.xml @@ -137,10 +137,6 @@ - - - - @@ -178,8 +174,7 @@ - - + diff --git a/plugins/devkit/intellij.devkit.compose/BUILD.bazel b/plugins/devkit/intellij.devkit.compose/BUILD.bazel index b60e41afa3ac7..bcbdfed78fdfa 100644 --- a/plugins/devkit/intellij.devkit.compose/BUILD.bazel +++ b/plugins/devkit/intellij.devkit.compose/BUILD.bazel @@ -7,7 +7,6 @@ create_kotlinc_options( opt_in = [ "androidx.compose.foundation.ExperimentalFoundationApi", "kotlinx.coroutines.ExperimentalCoroutinesApi", - "org.jetbrains.icons.api.ExperimentalIconsApi", ], plugin_options = ["plugin:androidx.compose.compiler.plugins.kotlin:generateFunctionKeyMetaAnnotations=true"] ) diff --git a/plugins/devkit/intellij.devkit.compose/intellij.devkit.compose.iml b/plugins/devkit/intellij.devkit.compose/intellij.devkit.compose.iml index 966710d185fd0..61f8a6fd1f7f1 100644 --- a/plugins/devkit/intellij.devkit.compose/intellij.devkit.compose.iml +++ b/plugins/devkit/intellij.devkit.compose/intellij.devkit.compose.iml @@ -4,7 +4,7 @@ - diff --git a/plugins/devkit/intellij.devkit.compose/src/showcase/ComposeShowcase.kt b/plugins/devkit/intellij.devkit.compose/src/showcase/ComposeShowcase.kt index bb8af528b98e4..414d7743f93b5 100644 --- a/plugins/devkit/intellij.devkit.compose/src/showcase/ComposeShowcase.kt +++ b/plugins/devkit/intellij.devkit.compose/src/showcase/ComposeShowcase.kt @@ -1,16 +1,36 @@ // 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.devkit.compose.showcase -import androidx.compose.animation.core.* +import androidx.compose.animation.core.EaseInOut +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.TooltipArea import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.foundation.text.selection.SelectionContainer -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -42,7 +62,20 @@ import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.foundation.theme.OverrideDarkMode import org.jetbrains.jewel.ui.Orientation import org.jetbrains.jewel.ui.Outline -import org.jetbrains.jewel.ui.component.* +import org.jetbrains.jewel.ui.component.CheckboxRow +import org.jetbrains.jewel.ui.component.DefaultButton +import org.jetbrains.jewel.ui.component.Divider +import org.jetbrains.jewel.ui.component.Icon +import org.jetbrains.jewel.ui.component.IconButton +import org.jetbrains.jewel.ui.component.Link +import org.jetbrains.jewel.ui.component.OutlinedButton +import org.jetbrains.jewel.ui.component.RadioButtonRow +import org.jetbrains.jewel.ui.component.TabData +import org.jetbrains.jewel.ui.component.TabStrip +import org.jetbrains.jewel.ui.component.Text +import org.jetbrains.jewel.ui.component.TextField +import org.jetbrains.jewel.ui.component.Tooltip +import org.jetbrains.jewel.ui.component.VerticallyScrollableContainer import org.jetbrains.jewel.ui.icons.AllIconsKeys import org.jetbrains.jewel.ui.theme.defaultTabStyle import org.jetbrains.jewel.ui.theme.tooltipStyle diff --git a/plugins/devkit/intellij.devkit.compose/src/showcase/Icons.kt b/plugins/devkit/intellij.devkit.compose/src/showcase/Icons.kt index 95600b326cb75..fd748e3cd9abe 100644 --- a/plugins/devkit/intellij.devkit.compose/src/showcase/Icons.kt +++ b/plugins/devkit/intellij.devkit.compose/src/showcase/Icons.kt @@ -25,7 +25,7 @@ import kotlinx.coroutines.delay import kotlinx.serialization.json.Json import org.jetbrains.icons.Icon import org.jetbrains.icons.IconManager -import org.jetbrains.icons.deferredIconAsync +import org.jetbrains.icons.deferredIcon import org.jetbrains.icons.design.Circle import org.jetbrains.icons.design.IconAlign import org.jetbrains.icons.design.percent @@ -136,7 +136,7 @@ internal fun Icons(project: Project) { ShowcaseIcon( deferredIcon.toNewIcon(), deferredIcon, - "Deferred Icon" + "Legacy Deferred Icon" ) ) @@ -149,7 +149,7 @@ internal fun Icons(project: Project) { }) val json = Json { serializersModule = IconManager.getInstance().getSerializersModule() } - val dynIcon = deferredIconAsync(missingIcon) { + val dynIcon = deferredIcon(missingIcon) { delay(5000) icon { icon(missingIcon, modifier = IconModifier.patchSvg { @@ -164,7 +164,7 @@ internal fun Icons(project: Project) { ShowcaseIcon( dynIcon, null, - "Dynamic Icon" + "Deferred Icon" ) }) @@ -172,7 +172,7 @@ internal fun Icons(project: Project) { ShowcaseIcon( deserialized, null, - "Deserialized Dynamic Icon" + "Deserialized Deferred Icon" ) }) From 8a68150f9d14c8e0c7447e8070235ec12a4bd58d Mon Sep 17 00:00:00 2001 From: Jakub Senohrabek Date: Wed, 4 Mar 2026 17:11:45 +0100 Subject: [PATCH 9/9] IJPL-176416 New Icons API - fixed dynamic scaling and test circular dependency GitOrigin-RevId: 5b0162d5dca56987bbb014ff7f34a78b4bb9582b --- .idea/modules.xml | 1 + build/bazel-generated-file-list.txt | 1 + .../build/productLayout/CoreModuleSets.kt | 4 +- platform/core-ui/BUILD.bazel | 1 + platform/core-ui/module-content.yaml | 3 +- platform/icons-api/BUILD.bazel | 12 +++ platform/icons-api/module-content.yaml | 3 - platform/icons-api/rendering/BUILD.bazel | 16 ++++ .../icons-api/rendering/module-content.yaml | 3 - .../src/org/jetbrains/icons/DeferredIcon.kt | 2 +- platform/icons-impl/BUILD.bazel | 22 +++++ platform/icons-impl/intellij/BUILD.bazel | 83 ++++++++++--------- .../intellij.platform.icons.impl.intellij.iml | 17 ++-- .../icons-impl/intellij/module-content.yaml | 5 +- .../intellij.platform.icons.impl.intellij.xml | 4 + .../impl/intellij/IntelliJIconManager.kt | 10 +++ .../intellij/ModuleImageResourceLocation.kt | 1 - .../impl/intellij/rendering/SwingIcon.kt | 26 ++++-- .../images/IntelliJImageResourceProvider.kt | 7 +- .../intellij/DefaultIconSerializationTest.kt | 34 -------- .../icons/impl/intellij/DynamicIconTest.kt | 60 -------------- .../icons-impl/intellij/tests/BUILD.bazel | 75 +++++++++++++++++ ...lij.platform.icons.impl.intellij.tests.iml | 46 ++++++++++ .../jetbrains/icons/impl/intellij/IconTest.kt | 26 ++++++ platform/icons-impl/module-content.yaml | 3 - .../icons/impl/DefaultIconManager.kt | 2 - .../icons/impl}/StringIconIdentifier.kt | 7 +- .../rendering/layers/DefaultLayerLayout.kt | 2 +- .../intellij.moduleSets.core.platform.xml | 1 + platform/jewel/ide-laf-bridge/BUILD.bazel | 2 +- .../int-ui/int-ui-standalone/BUILD.bazel | 4 +- platform/jewel/samples/showcase/BUILD.bazel | 2 +- platform/jewel/ui/BUILD.bazel | 7 +- .../ui/icon/ComposeBitmapImageResource.kt | 29 +++++-- .../jewel/ui/icon/ComposePaintingApi.kt | 13 +-- platform/lang-impl/module-content.yaml | 2 + platform/platform-impl/bootstrap/BUILD.bazel | 2 + .../com/intellij/platform/ide/bootstrap/ui.kt | 4 +- .../META-INF/intellij.moduleSets.core.ide.xml | 1 + .../intellij.moduleSets.core.lang.xml | 2 +- .../intellij.moduleSets.essential.minimal.xml | 2 +- .../intellij.moduleSets.essential.xml | 2 +- .../intellij.moduleSets.ide.common.xml | 2 +- platform/util/BUILD.bazel | 6 ++ .../intellij.devkit.compose/BUILD.bazel | 4 + .../showcase/ComposePerformanceDemoAction.kt | 4 +- .../src/showcase/ComposeShowcaseAction.kt | 2 +- .../src/showcase/Icons.kt | 22 +++++ 48 files changed, 385 insertions(+), 204 deletions(-) delete mode 100644 platform/icons-api/module-content.yaml delete mode 100644 platform/icons-api/rendering/module-content.yaml delete mode 100644 platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DefaultIconSerializationTest.kt delete mode 100644 platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DynamicIconTest.kt create mode 100644 platform/icons-impl/intellij/tests/BUILD.bazel create mode 100644 platform/icons-impl/intellij/tests/intellij.platform.icons.impl.intellij.tests.iml create mode 100644 platform/icons-impl/intellij/tests/test/org/jetbrains/icons/impl/intellij/IconTest.kt delete mode 100644 platform/icons-impl/module-content.yaml rename platform/icons-impl/src/{ => org/jetbrains/icons/impl}/StringIconIdentifier.kt (94%) diff --git a/.idea/modules.xml b/.idea/modules.xml index 250f712de2f6e..64e00b6209255 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -1195,6 +1195,7 @@ + diff --git a/build/bazel-generated-file-list.txt b/build/bazel-generated-file-list.txt index f089d6e798500..8d4c5b4553126 100644 --- a/build/bazel-generated-file-list.txt +++ b/build/bazel-generated-file-list.txt @@ -624,6 +624,7 @@ platform/icons-api platform/icons-api/rendering platform/icons-impl platform/icons-impl/intellij +platform/icons-impl/intellij/tests platform/ide-core platform/ide-core-impl platform/ide-core/plugins diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CoreModuleSets.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CoreModuleSets.kt index a12f303805ca7..e64dc88945ea3 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CoreModuleSets.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/productLayout/CoreModuleSets.kt @@ -253,6 +253,8 @@ object CoreModuleSets { embeddedModule("intellij.platform.util.ui") embeddedModule("intellij.platform.util.coroutines") + embeddedModule("intellij.platform.icons.impl.intellij") + embeddedModule("intellij.platform.locking.impl") embeddedModule("intellij.platform.core") @@ -343,8 +345,6 @@ object CoreModuleSets { // IDE implementation (depends on lang.core, so must come after) embeddedModule("intellij.platform.ide.impl") - embeddedModule("intellij.platform.icons.impl.intellij") - // Additional dependencies specific to lang.impl and ide.impl embeddedModule("intellij.platform.ide.concurrency") embeddedModule("intellij.platform.builtInServer") diff --git a/platform/core-ui/BUILD.bazel b/platform/core-ui/BUILD.bazel index 4ecc983bcce0c..9188b37facaaa 100644 --- a/platform/core-ui/BUILD.bazel +++ b/platform/core-ui/BUILD.bazel @@ -34,6 +34,7 @@ jvm_library( "//platform/core-api:core_test_lib", "//platform/util:util-ui_test_lib", "//libraries/hash4j:hash4j_test_lib", + "//platform/icons-api:icons-api_test_lib", ] ) ### auto-generated section `build intellij.platform.core.ui` end \ No newline at end of file diff --git a/platform/core-ui/module-content.yaml b/platform/core-ui/module-content.yaml index c07b56e8df3f1..b66af348e1874 100644 --- a/platform/core-ui/module-content.yaml +++ b/platform/core-ui/module-content.yaml @@ -1,3 +1,4 @@ - name: dist.all/lib/intellij.platform.core.ui.jar modules: - - name: intellij.platform.core.ui \ No newline at end of file + - name: intellij.platform.core.ui + - name: intellij.platform.icons.api \ No newline at end of file diff --git a/platform/icons-api/BUILD.bazel b/platform/icons-api/BUILD.bazel index 17ff9b751c4c3..311cfd3baf8dc 100644 --- a/platform/icons-api/BUILD.bazel +++ b/platform/icons-api/BUILD.bazel @@ -13,4 +13,16 @@ jvm_library( "@lib//:jetbrains-annotations", ] ) + +jvm_library( + name = "icons-api_test_lib", + module_name = "intellij.platform.icons.api", + visibility = ["//visibility:public"], + srcs = glob([], allow_empty = True), + runtime_deps = [ + ":icons-api", + "//libraries/kotlinx/coroutines/core:core_test_lib", + "//libraries/kotlinx/serialization/core:core_test_lib", + ] +) ### auto-generated section `build intellij.platform.icons.api` end \ No newline at end of file diff --git a/platform/icons-api/module-content.yaml b/platform/icons-api/module-content.yaml deleted file mode 100644 index c554822f8bd31..0000000000000 --- a/platform/icons-api/module-content.yaml +++ /dev/null @@ -1,3 +0,0 @@ -- name: dist.all/lib/intellij.platform.icons.api.jar - modules: - - name: intellij.platform.icons.api \ No newline at end of file diff --git a/platform/icons-api/rendering/BUILD.bazel b/platform/icons-api/rendering/BUILD.bazel index a2797ddd5f23b..6f84826b43e4d 100644 --- a/platform/icons-api/rendering/BUILD.bazel +++ b/platform/icons-api/rendering/BUILD.bazel @@ -14,4 +14,20 @@ jvm_library( ], exports = ["//platform/icons-api"] ) + +jvm_library( + name = "rendering_test_lib", + module_name = "intellij.platform.icons.api.rendering", + visibility = ["//visibility:public"], + srcs = glob([], allow_empty = True), + exports = [ + "//platform/icons-api", + "//platform/icons-api:icons-api_test_lib", + ], + runtime_deps = [ + ":rendering", + "//libraries/kotlinx/coroutines/core:core_test_lib", + "//platform/icons-api:icons-api_test_lib", + ] +) ### auto-generated section `build intellij.platform.icons.api.rendering` end \ No newline at end of file diff --git a/platform/icons-api/rendering/module-content.yaml b/platform/icons-api/rendering/module-content.yaml deleted file mode 100644 index 0340cf0c9059b..0000000000000 --- a/platform/icons-api/rendering/module-content.yaml +++ /dev/null @@ -1,3 +0,0 @@ -- name: dist.all/lib/intellij.platform.icons.api.rendering.jar - modules: - - name: intellij.platform.icons.api.rendering \ No newline at end of file diff --git a/platform/icons-api/src/org/jetbrains/icons/DeferredIcon.kt b/platform/icons-api/src/org/jetbrains/icons/DeferredIcon.kt index 25c62bdcbe6bb..7f4cd00af8735 100644 --- a/platform/icons-api/src/org/jetbrains/icons/DeferredIcon.kt +++ b/platform/icons-api/src/org/jetbrains/icons/DeferredIcon.kt @@ -9,7 +9,7 @@ import org.jetbrains.annotations.ApiStatus * Unlike the older API, to force evaluation or get the resolved icon, IconManager should be used. * * @see IconManager.deferredIcon - * @see IconManager.forceEvaluation + * @see IconManager.forceEvaluationnnnn */ @ApiStatus.Experimental interface DeferredIcon: Icon { diff --git a/platform/icons-impl/BUILD.bazel b/platform/icons-impl/BUILD.bazel index a32794262b170..fd2dbda804948 100644 --- a/platform/icons-impl/BUILD.bazel +++ b/platform/icons-impl/BUILD.bazel @@ -19,4 +19,26 @@ jvm_library( "//platform/icons-api/rendering", ] ) + +jvm_library( + name = "icons-impl_test_lib", + module_name = "intellij.platform.icons.impl", + visibility = ["//visibility:public"], + srcs = glob([], allow_empty = True), + exports = [ + "//platform/icons-api", + "//platform/icons-api:icons-api_test_lib", + "//platform/icons-api/rendering", + "//platform/icons-api/rendering:rendering_test_lib", + ], + runtime_deps = [ + ":icons-impl", + "//libraries/kotlinx/coroutines/core:core_test_lib", + "//platform/icons-api:icons-api_test_lib", + "//platform/icons-api/rendering:rendering_test_lib", + "//platform/icons", + "//platform/icons:icons_test_lib", + "//libraries/kotlinx/serialization/core:core_test_lib", + ] +) ### auto-generated section `build intellij.platform.icons.impl` end \ No newline at end of file diff --git a/platform/icons-impl/intellij/BUILD.bazel b/platform/icons-impl/intellij/BUILD.bazel index 28039ec663eaa..22f96bae67184 100644 --- a/platform/icons-impl/intellij/BUILD.bazel +++ b/platform/icons-impl/intellij/BUILD.bazel @@ -1,5 +1,5 @@ ### auto-generated section `build intellij.platform.icons.impl.intellij` start -load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") +load("@rules_jvm//:jvm.bzl", "jvm_library", "jvm_provided_library", "resourcegroup") resourcegroup( name = "intellij_resources", @@ -7,6 +7,21 @@ resourcegroup( strip_prefix = "resources" ) +jvm_provided_library( + name = "platform_icons-api_provided", + lib = "//platform/icons-api" +) + +jvm_provided_library( + name = "platform_icons-api_rendering_provided", + lib = "//platform/icons-api/rendering" +) + +jvm_provided_library( + name = "platform_icons-impl_provided", + lib = "//platform/icons-impl" +) + jvm_library( name = "intellij", module_name = "intellij.platform.icons.impl.intellij", @@ -16,9 +31,7 @@ jvm_library( deps = [ "@lib//:kotlin-stdlib", "//libraries/kotlinx/coroutines/core", - "//platform/icons-api", - "//platform/icons-api/rendering", - "//platform/icons-impl", + "//libraries/kotlinx/serialization/core", "//platform/util", "//platform/util:util-ui", "//platform/core-api:core", @@ -27,9 +40,11 @@ jvm_library( "//platform/core-impl", "//platform/platform-impl:ide-impl", "//platform/core-ui", - "//libraries/kotlinx/serialization/core", "@lib//:jetbrains-annotations", "//platform/util/concurrency", + ":platform_icons-api_provided", + ":platform_icons-api_rendering_provided", + ":platform_icons-impl_provided", ], exports = [ "//platform/icons-api", @@ -40,48 +55,36 @@ jvm_library( jvm_library( name = "intellij_test_lib", + module_name = "intellij.platform.icons.impl.intellij", visibility = ["//visibility:public"], - srcs = glob(["test/**/*.kt", "test/**/*.java", "test/**/*.form"], allow_empty = True), - associates = [":intellij"], - deps = [ - "@lib//:kotlin-stdlib", - "//libraries/kotlinx/coroutines/core", + srcs = glob([], allow_empty = True), + exports = [ "//platform/icons-api", + "//platform/icons-api:icons-api_test_lib", "//platform/icons-api/rendering", + "//platform/icons-api/rendering:rendering_test_lib", "//platform/icons-impl", - "//platform/util", - "//platform/util:util-ui", - "//platform/core-api:core", - "//platform/platform-impl/rpc", - "//fleet/util/core", - "//platform/core-impl", - "//libraries/junit5", - "//platform/testFramework/junit5", - "//platform/testFramework/junit5:junit5_test_lib", - "//platform/platform-impl:ide-impl", - "//platform/core-ui", - "//libraries/kotlinx/serialization/json", - "@lib//:junit5", - "@lib//:junit5Jupiter", - "@lib//:assert_j", - "//libraries/kotlinx/serialization/core", - "@lib//:jetbrains-annotations", - "//platform/util/concurrency", - "//platform/util/concurrency:concurrency_test_lib", + "//platform/icons-impl:icons-impl_test_lib", ], - exports = [ + runtime_deps = [ + ":intellij", + "//libraries/kotlinx/coroutines/core:core_test_lib", + "//libraries/kotlinx/serialization/core:core_test_lib", "//platform/icons-api", + "//platform/icons-api:icons-api_test_lib", "//platform/icons-api/rendering", + "//platform/icons-api/rendering:rendering_test_lib", "//platform/icons-impl", + "//platform/icons-impl:icons-impl_test_lib", + "//platform/util:util_test_lib", + "//platform/util:util-ui_test_lib", + "//platform/core-api:core_test_lib", + "//platform/platform-impl/rpc:rpc_test_lib", + "//fleet/util/core:core_test_lib", + "//platform/core-impl:core-impl_test_lib", + "//platform/platform-impl:ide-impl_test_lib", + "//platform/core-ui:core-ui_test_lib", + "//platform/util/concurrency:concurrency_test_lib", ] ) -### auto-generated section `build intellij.platform.icons.impl.intellij` end - -### auto-generated section `test intellij.platform.icons.impl.intellij` start -load("@community//build:tests-options.bzl", "jps_test") - -jps_test( - name = "intellij_test", - runtime_deps = [":intellij_test_lib"] -) -### auto-generated section `test intellij.platform.icons.impl.intellij` end \ No newline at end of file +### auto-generated section `build intellij.platform.icons.impl.intellij` end \ No newline at end of file diff --git a/platform/icons-impl/intellij/intellij.platform.icons.impl.intellij.iml b/platform/icons-impl/intellij/intellij.platform.icons.impl.intellij.iml index 63f2b94d88f88..2e4055576f9e2 100644 --- a/platform/icons-impl/intellij/intellij.platform.icons.impl.intellij.iml +++ b/platform/icons-impl/intellij/intellij.platform.icons.impl.intellij.iml @@ -27,30 +27,23 @@ - - - - - + + + + + - - - - - - - diff --git a/platform/icons-impl/intellij/module-content.yaml b/platform/icons-impl/intellij/module-content.yaml index 0094e28a70744..3f0be96e40100 100644 --- a/platform/icons-impl/intellij/module-content.yaml +++ b/platform/icons-impl/intellij/module-content.yaml @@ -1,6 +1,3 @@ - name: dist.all/lib/intellij.platform.icons.impl.intellij.jar modules: - - name: intellij.platform.icons.impl.intellij - libraries: - jetbrains.kotlinx.serialization.core.jvm: - - name: $MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-core-jvm/1/kotlinx-serialization-core-jvm-1.jar \ No newline at end of file + - name: intellij.platform.icons.impl.intellij \ No newline at end of file diff --git a/platform/icons-impl/intellij/resources/intellij.platform.icons.impl.intellij.xml b/platform/icons-impl/intellij/resources/intellij.platform.icons.impl.intellij.xml index 7dcda8ce09813..64d37ff3a6822 100644 --- a/platform/icons-impl/intellij/resources/intellij.platform.icons.impl.intellij.xml +++ b/platform/icons-impl/intellij/resources/intellij.platform.icons.impl.intellij.xml @@ -29,5 +29,9 @@ + diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt index e7e4b871a5150..f25a296979c0b 100644 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/IntelliJIconManager.kt @@ -23,7 +23,11 @@ import org.jetbrains.icons.impl.DeferredIconResolver import org.jetbrains.icons.impl.intellij.custom.CustomIconLayerRegistration import org.jetbrains.icons.impl.intellij.custom.CustomLegacyIconSerializer import org.jetbrains.icons.impl.intellij.design.IntelliJIconDesigner +import org.jetbrains.icons.impl.intellij.rendering.IntelliJIconRendererManager import org.jetbrains.icons.impl.intellij.rendering.SwingIcon +import org.jetbrains.icons.impl.intellij.rendering.images.IntelliJImageResourceProvider +import org.jetbrains.icons.rendering.IconRendererManager +import org.jetbrains.icons.rendering.ImageResourceProvider import java.lang.ref.WeakReference import kotlin.getValue @@ -90,6 +94,12 @@ class IntelliJIconManager : DefaultIconManager() { } companion object { + fun activate() { + org.jetbrains.icons.IconManager.activate(IntelliJIconManager()) + IconRendererManager.activate(IntelliJIconRendererManager()) + ImageResourceProvider.activate(IntelliJImageResourceProvider()) + } + internal fun getPluginAndModuleId(classLoader: ClassLoader): Pair { if (classLoader is PluginAwareClassLoader) { return classLoader.pluginId.idString to classLoader.moduleId diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/ModuleImageResourceLocation.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/ModuleImageResourceLocation.kt index 92fb45f3f6cca..68c4133ceb06c 100644 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/ModuleImageResourceLocation.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/ModuleImageResourceLocation.kt @@ -1,7 +1,6 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.impl.intellij -import com.intellij.ide.plugins.cl.PluginAwareClassLoader import kotlinx.serialization.Serializable import org.jetbrains.icons.ImageResourceLocation diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIcon.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIcon.kt index 98d0ef47e4cd5..643453710cea6 100644 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIcon.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/SwingIcon.kt @@ -7,6 +7,7 @@ import org.jetbrains.icons.rendering.IconRendererManager import org.jetbrains.icons.rendering.RenderingContext import org.jetbrains.icons.rendering.ScalingContext import org.jetbrains.icons.impl.rendering.layers.applyTo +import org.jetbrains.icons.rendering.Dimensions import java.awt.Component import java.awt.Graphics import java.awt.Graphics2D @@ -23,23 +24,34 @@ class SwingIcon( renderer.calculateExpectedDimensions(SwingScalingContext(1f)) } - override fun paintIcon(c: Component, g: Graphics, x: Int, y: Int) { + override fun paintIcon(c: Component?, g: Graphics, x: Int, y: Int) { val scaling = getScaling(g) - withLayer(scaling, c, g, x, y) { newGraphics -> - val swingApi = SwingPaintingApi(c, newGraphics, 0, 0, scaling = scaling) + val boundsSize = if (c != null) { + Dimensions(c.width, c.height) + } else Dimensions(dimensions.width, dimensions.height) + withLayer(scaling, boundsSize, g, x, y) { newGraphics -> + val swingApi = SwingPaintingApi( + c, + newGraphics, + 0, + 0, + scaling = scaling, + customWidth = boundsSize.width, + customHeight = boundsSize.height + ) renderer.render(swingApi) } } - private fun withLayer(scaling: ScalingContext, c: Component, g: Graphics, x: Int, y: Int, painting: (Graphics2D) -> Unit) { - val w = scaling.applyTo(c.width - x) - val h = scaling.applyTo(c.height - y) + private fun withLayer(scaling: ScalingContext, boundsSize: Dimensions, g: Graphics, x: Int, y: Int, painting: (Graphics2D) -> Unit) { + val w = scaling.applyTo(boundsSize.width - x) + val h = scaling.applyTo(boundsSize.height - y) if (w < 0 || h < 0) return val img = BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB) val sublayer = img.createGraphics() try { painting(sublayer as Graphics2D) - g.drawImage(img, x, y, c.width, c.height, 0, 0, img.width, img.height, null) + g.drawImage(img, x, y, boundsSize.width, boundsSize.height, 0, 0, img.width, img.height, null) } finally { sublayer.dispose() } diff --git a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/images/IntelliJImageResourceProvider.kt b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/images/IntelliJImageResourceProvider.kt index bc004d5514260..003831782addb 100644 --- a/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/images/IntelliJImageResourceProvider.kt +++ b/platform/icons-impl/intellij/src/org/jetbrains/icons/impl/intellij/rendering/images/IntelliJImageResourceProvider.kt @@ -4,13 +4,18 @@ package org.jetbrains.icons.impl.intellij.rendering.images import org.jetbrains.icons.rendering.ImageModifiers import org.jetbrains.icons.rendering.ImageResource import org.jetbrains.icons.ImageResourceLocation +import org.jetbrains.icons.impl.intellij.ModuleImageResourceLocation import org.jetbrains.icons.impl.intellij.rendering.ImageResourceLoaderExtension import org.jetbrains.icons.impl.rendering.DefaultImageResourceProvider import javax.swing.Icon class IntelliJImageResourceProvider: DefaultImageResourceProvider() { override fun loadImage(location: ImageResourceLocation, imageModifiers: ImageModifiers?): ImageResource { - val loader = ImageResourceLoaderExtension.getLoaderFor(location) + val loader = if (location is ModuleImageResourceLocation) { + ModuleImageResourceLoader() + } else { + ImageResourceLoaderExtension.getLoaderFor(location) + } if (loader == null) error("Cannot find loader for location: $location") return loader.loadGenericImage(location, imageModifiers) ?: error("Cannot load image for location: $location") } diff --git a/platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DefaultIconSerializationTest.kt b/platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DefaultIconSerializationTest.kt deleted file mode 100644 index 5e85c311eb5a8..0000000000000 --- a/platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DefaultIconSerializationTest.kt +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.jetbrains.icons.impl.intellij - -import com.intellij.ide.rpc.deserializeFromRpc -import com.intellij.ide.rpc.serializeToRpc -import com.intellij.openapi.application.Application -import com.intellij.openapi.application.ApplicationManager -import com.intellij.testFramework.junit5.TestApplication -import org.jetbrains.icons.Icon -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.Test - -@TestApplication -class DefaultIconSerializationTest { - @Test - fun `should serialize and deserialize default icon`() { - val iconManager = IntelliJIconManager() - val icon2 = iconManager.icon { - image("test.svg") - } - val icon1 = iconManager.icon { - row { - column { - image("test.png", IntelliJIconManager::class.java.classLoader) - image("test2.svg", IntelliJIconManager::class.java.classLoader) - } - icon(icon2) - } - } - val serialized = serializeToRpc(icon1) - val deserialized = deserializeFromRpc(serialized, Icon::class) - assertTrue(deserialized == icon1) - } -} \ No newline at end of file diff --git a/platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DynamicIconTest.kt b/platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DynamicIconTest.kt deleted file mode 100644 index a08f7a3308259..0000000000000 --- a/platform/icons-impl/intellij/test/org/jetbrains/icons/impl/intellij/DynamicIconTest.kt +++ /dev/null @@ -1,60 +0,0 @@ -// Not possible before DynamicIcon can be synced cross-serialization - - -/*// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.jetbrains.icons.impl.intellij - -import com.intellij.testFramework.junit5.TestApplication -import com.intellij.testFramework.junit5.fixture.TestFixtures -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.serialization.json.Json -import org.assertj.core.api.Assertions.assertThat -import org.jetbrains.icons.DynamicIcon -import org.jetbrains.annotations.ApiStatus -import org.jetbrains.icons.Icon -import org.jetbrains.icons.IconManager -import org.jetbrains.icons.dynamicIcon -import org.jetbrains.icons.imageIcon -import org.jetbrains.icons.rendering.IconRendererManager -import org.jetbrains.icons.rendering.RenderingContext -import org.jetbrains.icons.rendering.createRenderer -import org.junit.jupiter.api.Test - -@OptIn(DelicateCoroutinesApi::class) -@TestApplication -class DynamicIconTest { - - @Test - fun `should be properly updated after serialization and deserialization`() { - IntelliJIconManager.activate() - runBlocking { - val binary = imageIcon("fileTypes/binaryData.svg") - val css = imageIcon("fileTypes/css.svg") - val dynamicIcon = dynamicIcon(binary) - val json = Json { - serializersModule = IconManager.getInstance().getSerializersModule() - } - val serialized = json.encodeToString(dynamicIcon) - val deserialized = json.decodeFromString(serialized) - var lastChangeId = 0 - val flow = IconRendererManager.createUpdateFlow(this) { changeId -> - lastChangeId = changeId - } - assertThat(deserialized).isInstanceOf(DynamicIcon::class.java) - val deserializedDynamicIcon = deserialized as DynamicIcon - val icon = deserializedDynamicIcon.createRenderer(RenderingContext(flow)) - assertThat(deserializedDynamicIcon.getCurrentIcon()).isEqualTo(binary) - val asyncTask = GlobalScope.launch { - delay(1000) - dynamicIcon.swap(css) - } - asyncTask.join() - assertThat(deserializedDynamicIcon.getCurrentIcon()).isEqualTo(css) - assertThat(lastChangeId).isGreaterThan(0) - } - } -}*/ \ No newline at end of file diff --git a/platform/icons-impl/intellij/tests/BUILD.bazel b/platform/icons-impl/intellij/tests/BUILD.bazel new file mode 100644 index 0000000000000..5b098bec845f6 --- /dev/null +++ b/platform/icons-impl/intellij/tests/BUILD.bazel @@ -0,0 +1,75 @@ +### auto-generated section `build intellij.platform.icons.impl.intellij.tests` start +load("@rules_jvm//:jvm.bzl", "jvm_library") + +jvm_library( + name = "tests", + visibility = ["//visibility:public"], + srcs = glob([], allow_empty = True), + exports = [ + "//platform/icons-api", + "//platform/icons-api/rendering", + "//platform/icons-impl", + "//platform/icons-impl/intellij", + ], + runtime_deps = [ + "@lib//:kotlin-stdlib", + "//libraries/kotlinx/coroutines/core", + "//platform/icons-api", + "//platform/icons-api/rendering", + "//platform/icons-impl", + "//platform/icons-impl/intellij", + ] +) + +jvm_library( + name = "tests_test_lib", + module_name = "intellij.platform.icons.impl.intellij.tests", + visibility = ["//visibility:public"], + srcs = glob(["test/**/*.kt", "test/**/*.java", "test/**/*.form"], allow_empty = True), + deps = [ + "@lib//:kotlin-stdlib", + "//libraries/kotlinx/coroutines/core", + "//libraries/kotlinx/coroutines/core:core_test_lib", + "//platform/icons-api", + "//platform/icons-api:icons-api_test_lib", + "//platform/icons-api/rendering", + "//platform/icons-api/rendering:rendering_test_lib", + "//platform/icons-impl", + "//platform/icons-impl:icons-impl_test_lib", + "//platform/icons-impl/intellij", + "//platform/icons-impl/intellij:intellij_test_lib", + "//libraries/junit5", + "//libraries/junit5:junit5_test_lib", + "//libraries/kotlinx/serialization/json", + "//libraries/kotlinx/serialization/json:json_test_lib", + "@lib//:assert_j", + "@lib//:junit5", + "//platform/testFramework/junit5", + "//platform/testFramework/junit5:junit5_test_lib", + "//platform/icons", + "//platform/icons:icons_test_lib", + "//platform/testFramework", + "//platform/testFramework:testFramework_test_lib", + ], + exports = [ + "//platform/icons-api", + "//platform/icons-api:icons-api_test_lib", + "//platform/icons-api/rendering", + "//platform/icons-api/rendering:rendering_test_lib", + "//platform/icons-impl", + "//platform/icons-impl:icons-impl_test_lib", + "//platform/icons-impl/intellij", + "//platform/icons-impl/intellij:intellij_test_lib", + ], + runtime_deps = [":tests"] +) +### auto-generated section `build intellij.platform.icons.impl.intellij.tests` end + +### auto-generated section `test intellij.platform.icons.impl.intellij.tests` start +load("@community//build:tests-options.bzl", "jps_test") + +jps_test( + name = "tests_test", + runtime_deps = [":tests_test_lib"] +) +### auto-generated section `test intellij.platform.icons.impl.intellij.tests` end \ No newline at end of file diff --git a/platform/icons-impl/intellij/tests/intellij.platform.icons.impl.intellij.tests.iml b/platform/icons-impl/intellij/tests/intellij.platform.icons.impl.intellij.tests.iml new file mode 100644 index 0000000000000..794e2ad44fe6f --- /dev/null +++ b/platform/icons-impl/intellij/tests/intellij.platform.icons.impl.intellij.tests.iml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + $USER_HOME$/Library/Caches/JetBrains/IntelliJIdea2025.3/kotlin-dist-for-ide/2.3.20-RC2/lib/kotlinx-serialization-compiler-plugin.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/platform/icons-impl/intellij/tests/test/org/jetbrains/icons/impl/intellij/IconTest.kt b/platform/icons-impl/intellij/tests/test/org/jetbrains/icons/impl/intellij/IconTest.kt new file mode 100644 index 0000000000000..9c16d71f984d4 --- /dev/null +++ b/platform/icons-impl/intellij/tests/test/org/jetbrains/icons/impl/intellij/IconTest.kt @@ -0,0 +1,26 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl.intellij + +import com.intellij.testFramework.junit5.TestApplication +import org.jetbrains.icons.imageIcon +import org.jetbrains.icons.swing.toSwingIcon +import org.junit.jupiter.api.Test +import java.awt.image.BufferedImage + +@TestApplication +class IconTest { + @Test + fun `should render icon to buffered image`() { + IntelliJIconManager.activate() + val icon = imageIcon("actions/addFile.svg", IconTest::class.java.classLoader) + + val swingIcon = icon.toSwingIcon() + val image = BufferedImage(swingIcon.iconWidth, swingIcon.iconHeight, BufferedImage.TYPE_INT_ARGB) + val g2 = image.createGraphics() + try { + swingIcon.paintIcon(null, g2, 0, 0) + } finally { + g2.dispose() + } + } +} diff --git a/platform/icons-impl/module-content.yaml b/platform/icons-impl/module-content.yaml deleted file mode 100644 index 25c0071bbabc8..0000000000000 --- a/platform/icons-impl/module-content.yaml +++ /dev/null @@ -1,3 +0,0 @@ -- name: dist.all/lib/intellij.platform.icons.impl.jar - modules: - - name: intellij.platform.icons.impl \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultIconManager.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultIconManager.kt index db1c90a1d107c..3c5935360b87a 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultIconManager.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/DefaultIconManager.kt @@ -1,9 +1,7 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.icons.impl -import StringIconIdentifier import kotlinx.serialization.KSerializer -import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.buildClassSerialDescriptor import kotlinx.serialization.descriptors.serialDescriptor diff --git a/platform/icons-impl/src/StringIconIdentifier.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/StringIconIdentifier.kt similarity index 94% rename from platform/icons-impl/src/StringIconIdentifier.kt rename to platform/icons-impl/src/org/jetbrains/icons/impl/StringIconIdentifier.kt index e0a2537f2ec7a..85436758af324 100644 --- a/platform/icons-impl/src/StringIconIdentifier.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/StringIconIdentifier.kt @@ -1,8 +1,9 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.icons.impl + import kotlinx.serialization.Serializable import org.jetbrains.icons.IconIdentifier -// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. - @Serializable class StringIconIdentifier( val value: String @@ -23,4 +24,4 @@ class StringIconIdentifier( override fun toString(): String { return value } -} +} \ No newline at end of file diff --git a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/DefaultLayerLayout.kt b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/DefaultLayerLayout.kt index 23388f299e8a0..0a9160b7e7555 100644 --- a/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/DefaultLayerLayout.kt +++ b/platform/icons-impl/src/org/jetbrains/icons/impl/rendering/layers/DefaultLayerLayout.kt @@ -79,7 +79,7 @@ open class DefaultLayerLayout( cutoutMargin: Float?, stroke: Color? ): DefaultLayerLayout { - return _root_ide_package_.org.jetbrains.icons.impl.rendering.layers.DefaultLayerLayout( + return DefaultLayerLayout( layerBounds, parentBounds, colorFilter, diff --git a/platform/ide-core/resources/META-INF/intellij.moduleSets.core.platform.xml b/platform/ide-core/resources/META-INF/intellij.moduleSets.core.platform.xml index 7e4afe5b8938e..349830d11cf60 100644 --- a/platform/ide-core/resources/META-INF/intellij.moduleSets.core.platform.xml +++ b/platform/ide-core/resources/META-INF/intellij.moduleSets.core.platform.xml @@ -118,6 +118,7 @@ + diff --git a/platform/jewel/ide-laf-bridge/BUILD.bazel b/platform/jewel/ide-laf-bridge/BUILD.bazel index 8511b12bfba2f..e6b5b0ba2e838 100644 --- a/platform/jewel/ide-laf-bridge/BUILD.bazel +++ b/platform/jewel/ide-laf-bridge/BUILD.bazel @@ -123,4 +123,4 @@ jps_test( name = "ide-laf-bridge_test", runtime_deps = [":ide-laf-bridge_test_lib"] ) -### auto-generated section `test intellij.platform.jewel.ideLafBridge` end +### auto-generated section `test intellij.platform.jewel.ideLafBridge` end \ No newline at end of file diff --git a/platform/jewel/int-ui/int-ui-standalone/BUILD.bazel b/platform/jewel/int-ui/int-ui-standalone/BUILD.bazel index b73dbf72a4841..130728a6e92c2 100644 --- a/platform/jewel/int-ui/int-ui-standalone/BUILD.bazel +++ b/platform/jewel/int-ui/int-ui-standalone/BUILD.bazel @@ -59,7 +59,9 @@ jvm_library( "//libraries/compose-runtime-desktop:compose-runtime-desktop_test_lib", "//platform/jewel/foundation:foundation_test_lib", "//libraries/jbr:jbr_test_lib", + "//platform/icons-api:icons-api_test_lib", + "//platform/icons-impl:icons-impl_test_lib", ], plugins = ["@lib//:compose-plugin"] ) -### auto-generated section `build intellij.platform.jewel.intUi.standalone` end +### auto-generated section `build intellij.platform.jewel.intUi.standalone` end \ No newline at end of file diff --git a/platform/jewel/samples/showcase/BUILD.bazel b/platform/jewel/samples/showcase/BUILD.bazel index f771b4442c6f5..873fe8d6f28e2 100644 --- a/platform/jewel/samples/showcase/BUILD.bazel +++ b/platform/jewel/samples/showcase/BUILD.bazel @@ -61,4 +61,4 @@ jvm_library( ], plugins = ["@lib//:compose-plugin"] ) -### auto-generated section `build intellij.platform.jewel.samples.showcase` end +### auto-generated section `build intellij.platform.jewel.samples.showcase` end \ No newline at end of file diff --git a/platform/jewel/ui/BUILD.bazel b/platform/jewel/ui/BUILD.bazel index 3bcc650fbeebb..fa68743554ee3 100644 --- a/platform/jewel/ui/BUILD.bazel +++ b/platform/jewel/ui/BUILD.bazel @@ -83,8 +83,11 @@ jvm_library( "//libraries/junit4", "//libraries/junit4:junit4_test_lib", "//platform/icons-api", + "//platform/icons-api:icons-api_test_lib", "//platform/icons-api/rendering", + "//platform/icons-api/rendering:rendering_test_lib", "//platform/icons-impl", + "//platform/icons-impl:icons-impl_test_lib", "//libraries/compose-foundation-desktop-junit", "//libraries/compose-foundation-desktop-junit:compose-foundation-desktop-junit_test_lib", ], @@ -96,7 +99,9 @@ jvm_library( "//libraries/compose-foundation-desktop", "//libraries/compose-foundation-desktop:compose-foundation-desktop_test_lib", "//platform/icons-api", + "//platform/icons-api:icons-api_test_lib", "//platform/icons-api/rendering", + "//platform/icons-api/rendering:rendering_test_lib", ], plugins = ["@lib//:compose-plugin"] ) @@ -109,4 +114,4 @@ jps_test( name = "ui_test", runtime_deps = [":ui_test_lib"] ) -### auto-generated section `test intellij.platform.jewel.ui` end +### auto-generated section `test intellij.platform.jewel.ui` end \ No newline at end of file diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeBitmapImageResource.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeBitmapImageResource.kt index 2a4c56b316e3a..e26324f9d888d 100644 --- a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeBitmapImageResource.kt +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposeBitmapImageResource.kt @@ -13,6 +13,7 @@ import org.jetbrains.skia.Bitmap import org.jetbrains.skia.ColorAlphaType import org.jetbrains.skia.ImageInfo import org.jetbrains.icons.impl.rendering.CachedGPUImageResourceHolder +import org.jetbrains.icons.rendering.Dimensions import org.jetbrains.skia.ColorType import org.jetbrains.skia.impl.BufferUtil @@ -51,35 +52,45 @@ internal fun BitmapImageResource.composeBitmap(): ImageBitmap { } ?: composeBitmapWithoutCaching().second } -internal fun RescalableImageResource.composeBitmap(scale: ImageScale): ImageBitmap { +internal fun RescalableImageResource.composeBitmap(scale: ImageScale): ImageBitmapView { val cache = (this as? GPUImageResourceHolder) val cached = cache?.getOrGenerateBitmap(SingleBitmapCache::class) { SingleBitmapCache() } if (cached != null) { return cached.getOrPut(this, scale) } else { - return scale(scale).composeBitmap() + return scale(scale).composeBitmap().toView() } } +internal fun ImageBitmap.toView(): ImageBitmapView { + return ImageBitmapView(this, Dimensions(this.width, this.height)) +} + +internal class ImageBitmapView( + val imageBitmap: ImageBitmap, + val size: Dimensions +) + private class SingleBitmapCache { private var lastBitmap: CachedBitmap? = null private class CachedBitmap( val dimensions: Bounds, - val composeBitmap: ImageBitmap, + var composeBitmap: ImageBitmapView, val bitmap: Bitmap ) - fun getOrPut(image: RescalableImageResource, scale: ImageScale): ImageBitmap { + fun getOrPut(image: RescalableImageResource, scale: ImageScale): ImageBitmapView { val last = lastBitmap val expectedDimensions = image.calculateExpectedDimensions(scale) if (last == null) { return createNewBitmap(image, scale, expectedDimensions) } else { - if (last.dimensions == expectedDimensions) { + if (last.composeBitmap.size == expectedDimensions) { return last.composeBitmap } else if (last.dimensions.canFit(expectedDimensions)) { last.bitmap.setPixelsFrom(image.scale(scale)) + last.composeBitmap = last.composeBitmap.adjustTo(expectedDimensions) return last.composeBitmap } else { return createNewBitmap(image, scale, expectedDimensions) @@ -87,11 +98,11 @@ private class SingleBitmapCache { } } - private fun createNewBitmap(image: RescalableImageResource, imageScale: ImageScale, dimensions: Bounds): ImageBitmap { + private fun createNewBitmap(image: RescalableImageResource, imageScale: ImageScale, dimensions: Bounds): ImageBitmapView { val (skia, compose) = image.scale(imageScale).composeBitmapWithoutCaching() val new = CachedBitmap( dimensions, - compose, + compose.toView(), skia ) lastBitmap = new @@ -99,6 +110,10 @@ private class SingleBitmapCache { } } +private fun ImageBitmapView.adjustTo(dimensions: Dimensions): ImageBitmapView { + return ImageBitmapView(imageBitmap, dimensions) +} + private fun BitmapImageResource.composeBitmapWithoutCaching(): Pair { val bitmap = Bitmap() bitmap.allocPixels(ImageInfo.makeS32(width, height, ColorAlphaType.UNPREMUL)) diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposePaintingApi.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposePaintingApi.kt index c0db6373af338..9cc8a3594a73d 100644 --- a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposePaintingApi.kt +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/icon/ComposePaintingApi.kt @@ -126,7 +126,7 @@ public class ComposePaintingApi( alpha: Float, colorFilter: ColorFilter? ) { - drawComposeImage(image.composeBitmap(), x, y, width, height, srcX, srcY, srcWidth, srcHeight, alpha, colorFilter) + drawComposeImage(image.composeBitmap().toView(), x, y, width, height, srcX, srcY, srcWidth, srcHeight, alpha, colorFilter) } private fun drawImage( @@ -176,7 +176,7 @@ public class ComposePaintingApi( } private fun drawComposeImage( - image: ImageBitmap, + image: ImageBitmapView, x: Int, y: Int, width: Int?, @@ -188,13 +188,14 @@ public class ComposePaintingApi( alpha: Float, colorFilter: ColorFilter? = null ) { - if (image.width == 0 || image.height == 0) return - val targetSize = IntSize(width ?: image.width, height ?: image.height) + if (image.imageBitmap.width == 0 || image.imageBitmap.height == 0) return + if (image.size.width == 0 || image.size.height == 0) return + val targetSize = IntSize(width ?: image.size.width, height ?: image.size.height) if (targetSize.width == 0 || targetSize.height == 0) return drawScope.drawImage( - image, + image.imageBitmap, IntOffset(srcX, srcY), - IntSize(srcWidth ?: image.width, srcHeight ?: image.height), + IntSize(srcWidth ?: image.size.width, srcHeight ?: image.size.height), dstOffset = IntOffset(x, y), dstSize = targetSize, alpha = alpha, diff --git a/platform/lang-impl/module-content.yaml b/platform/lang-impl/module-content.yaml index 042e844b3276d..2d863e30ca6d1 100644 --- a/platform/lang-impl/module-content.yaml +++ b/platform/lang-impl/module-content.yaml @@ -3,6 +3,8 @@ - name: intellij.platform.configurationStore.impl - name: intellij.platform.execution.impl - name: intellij.platform.foldings + - name: intellij.platform.icons.api.rendering + - name: intellij.platform.icons.impl - name: intellij.platform.ide.bootstrap - name: intellij.platform.ide.bootstrap.eel - name: intellij.platform.ide.bootstrap.kernel diff --git a/platform/platform-impl/bootstrap/BUILD.bazel b/platform/platform-impl/bootstrap/BUILD.bazel index dec24e1789086..ecc235f81a228 100644 --- a/platform/platform-impl/bootstrap/BUILD.bazel +++ b/platform/platform-impl/bootstrap/BUILD.bazel @@ -94,6 +94,8 @@ jvm_library( "//platform/eel-impl:eel-impl_test_lib", "//platform/platform-impl/initial-config-import:initial-config-import_test_lib", "//platform/eel-provider:eel-provider_test_lib", + "//platform/icons-impl:icons-impl_test_lib", + "//platform/icons-impl/intellij:intellij_test_lib", ] ) ### auto-generated section `build intellij.platform.ide.bootstrap` end \ No newline at end of file diff --git a/platform/platform-impl/bootstrap/src/com/intellij/platform/ide/bootstrap/ui.kt b/platform/platform-impl/bootstrap/src/com/intellij/platform/ide/bootstrap/ui.kt index b1670ea8f2d21..a1e5a7fb0ddc7 100644 --- a/platform/platform-impl/bootstrap/src/com/intellij/platform/ide/bootstrap/ui.kt +++ b/platform/platform-impl/bootstrap/src/com/intellij/platform/ide/bootstrap/ui.kt @@ -57,9 +57,7 @@ internal suspend fun initUi(initAwtToolkitJob: Job, isHeadless: Boolean, asyncSc IconManager.activate(iconManager) } span("new icon manager activation") { - org.jetbrains.icons.IconManager.activate(IntelliJIconManager()) - IconRendererManager.activate(IntelliJIconRendererManager()) - ImageResourceProvider.activate(IntelliJImageResourceProvider()) + IntelliJIconManager.activate() } } diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.ide.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.ide.xml index 4c4131e9934e4..c3bb9d77bf8d2 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.ide.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.ide.xml @@ -119,6 +119,7 @@ + diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.lang.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.lang.xml index de17a98711e5c..b1428d6388910 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.lang.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.core.lang.xml @@ -120,6 +120,7 @@ + @@ -169,7 +170,6 @@ - diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.minimal.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.minimal.xml index 65bb49a42b073..6cb7098139b16 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.minimal.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.minimal.xml @@ -121,6 +121,7 @@ + @@ -170,7 +171,6 @@ - diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.xml index 82511b4aba16f..a841c4ef439d4 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.essential.xml @@ -122,6 +122,7 @@ + @@ -171,7 +172,6 @@ - diff --git a/platform/platform-resources/generated/META-INF/intellij.moduleSets.ide.common.xml b/platform/platform-resources/generated/META-INF/intellij.moduleSets.ide.common.xml index bcd8085885db0..66299b07654ff 100644 --- a/platform/platform-resources/generated/META-INF/intellij.moduleSets.ide.common.xml +++ b/platform/platform-resources/generated/META-INF/intellij.moduleSets.ide.common.xml @@ -125,6 +125,7 @@ + @@ -174,7 +175,6 @@ - diff --git a/platform/util/BUILD.bazel b/platform/util/BUILD.bazel index a40a1b429590d..3c4b1b6fb05d9 100644 --- a/platform/util/BUILD.bazel +++ b/platform/util/BUILD.bazel @@ -73,6 +73,10 @@ jvm_library( "//platform/util/base:base_test_lib", "//platform/util/multiplatform", "//platform/util/multiplatform:multiplatform_test_lib", + "//platform/icons-api", + "//platform/icons-api:icons-api_test_lib", + "//platform/icons-api/rendering", + "//platform/icons-api/rendering:rendering_test_lib", ], runtime_deps = [ ":util", @@ -97,6 +101,8 @@ jvm_library( "//platform/util/coroutines:coroutines_test_lib", "//platform/util/multiplatform:multiplatform_test_lib", "//platform/eel:eel_test_lib", + "//platform/icons-api:icons-api_test_lib", + "//platform/icons-api/rendering:rendering_test_lib", ] ) ### auto-generated section `build intellij.platform.util` end diff --git a/plugins/devkit/intellij.devkit.compose/BUILD.bazel b/plugins/devkit/intellij.devkit.compose/BUILD.bazel index bcbdfed78fdfa..0de1a9024cd55 100644 --- a/plugins/devkit/intellij.devkit.compose/BUILD.bazel +++ b/plugins/devkit/intellij.devkit.compose/BUILD.bazel @@ -116,6 +116,10 @@ jvm_library( "//platform/util/jdom:jdom_test_lib", "//platform/external-system-impl:externalSystem-impl_test_lib", "//plugins/gradle:gradle_test_lib", + "//platform/platform-impl/rpc:rpc_test_lib", + "//fleet/util/core:core_test_lib", + "//libraries/kotlinx/serialization/json:json_test_lib", + "//libraries/kotlinx/serialization/core:core_test_lib", ], plugins = ["@lib//:compose-plugin"] ) diff --git a/plugins/devkit/intellij.devkit.compose/src/showcase/ComposePerformanceDemoAction.kt b/plugins/devkit/intellij.devkit.compose/src/showcase/ComposePerformanceDemoAction.kt index c6abe63d2db48..b02e89a1f5b43 100644 --- a/plugins/devkit/intellij.devkit.compose/src/showcase/ComposePerformanceDemoAction.kt +++ b/plugins/devkit/intellij.devkit.compose/src/showcase/ComposePerformanceDemoAction.kt @@ -43,6 +43,8 @@ import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.project.Project import com.intellij.openapi.ui.DialogWrapper +import org.jetbrains.icons.modifiers.IconModifier +import org.jetbrains.icons.modifiers.fillMaxSize import org.jetbrains.icons.swing.toNewIcon import org.jetbrains.jewel.bridge.compose import org.jetbrains.jewel.ui.component.Checkbox @@ -251,7 +253,7 @@ private fun createIconsComponent(): JComponent { val icons = mutableListOf() for (nested in AllIcons::class.java.nestMembers) { val fields = nested.declaredFields.filter { it.type.name.contains("Icon") } - icons.addAll(fields.map { (it.get(null) as Icon).toNewIcon() }) + icons.addAll(fields.map { (it.get(null) as Icon).toNewIcon(IconModifier.fillMaxSize()) }) } icons } diff --git a/plugins/devkit/intellij.devkit.compose/src/showcase/ComposeShowcaseAction.kt b/plugins/devkit/intellij.devkit.compose/src/showcase/ComposeShowcaseAction.kt index 4aece0e6d30aa..fa35f0c62e1ae 100644 --- a/plugins/devkit/intellij.devkit.compose/src/showcase/ComposeShowcaseAction.kt +++ b/plugins/devkit/intellij.devkit.compose/src/showcase/ComposeShowcaseAction.kt @@ -23,7 +23,7 @@ internal class ComposeShowcaseAction : DumbAwareAction() { override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT override fun update(e: AnActionEvent) { - e.presentation.isEnabledAndVisible = e.project != null && PsiUtil.isPluginProject(e.project!!) + e.presentation.isEnabledAndVisible = e.project != null // && PsiUtil.isPluginProject(e.project!!) } override fun actionPerformed(e: AnActionEvent) { diff --git a/plugins/devkit/intellij.devkit.compose/src/showcase/Icons.kt b/plugins/devkit/intellij.devkit.compose/src/showcase/Icons.kt index fd748e3cd9abe..3b35eb9bf9118 100644 --- a/plugins/devkit/intellij.devkit.compose/src/showcase/Icons.kt +++ b/plugins/devkit/intellij.devkit.compose/src/showcase/Icons.kt @@ -1,12 +1,21 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.devkit.compose.showcase +import androidx.compose.animation.core.EaseInOut +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.awt.SwingPanel @@ -54,6 +63,19 @@ internal fun Icons(project: Project) { val icons = mutableListOf() val missingIcon = remember { AllIcons.General.Error.toNewIcon(modifier = IconModifier.fillMaxSize()) } + val transition = rememberInfiniteTransition() + val iconSize by transition.animateFloat( + 30f, + 80f, + infiniteRepeatable(tween(durationMillis = 1000, easing = EaseInOut), repeatMode = RepeatMode.Reverse), + ) + + Box(Modifier.size(80.dp)) { + Icon(icon { + swingIcon(AllIcons.Actions.NewFolder, modifier = IconModifier.fillMaxSize()) + }, "New Folder", modifier = Modifier.size(iconSize.dp)) + } + val duration = 50L val animatedIcon = remember { icon {