From ee44190f3c15b10398b105aa4e71a191e85ed019 Mon Sep 17 00:00:00 2001 From: Daniil Kalinin Date: Mon, 20 Oct 2025 15:41:44 +0200 Subject: [PATCH] PY-82344 Implement `Welcome to unified PyCharm!` promo welcome screen banner, refactoring (cherry picked from commit b80df8b40facfbf114d0d8965756768cb799f198) IJ-CR-179688 GitOrigin-RevId: 06d66ae94cfc38b07ba27e8ea71e7f52d072e19d --- .../promo/backgrounds/promotion_bg.svg | 103 ---------------- .../PyCommunityToUnifiedPromoService.kt | 3 + ...PyCommunityToUnifiedWelcomeScreenBanner.kt | 67 ++--------- .../PyPromoSharedComponents.kt | 2 +- ...PycharmCommunityIdeImplPromotionIcons.java | 9 +- .../resources/backgrounds/promotion_bg.svg | 113 ++++++++++++++++++ python/ide/impl/resources/icons/close.svg | 4 + .../ide/impl/resources/icons/close_dark.svg | 4 + .../intellij.pycharm.community.ide.impl.xml | 4 + ...armCommunityCustomizationBundle.properties | 7 +- .../icons/PycharmCommunityIdeImplIcons.java | 26 ++++ ...mmunityToUnifiedWelcomeScreenBannerBase.kt | 93 ++++++++++++++ .../WelcomeToUnifiedWelcomeScreenBanner.kt | 112 +++++++++++++++++ 13 files changed, 375 insertions(+), 172 deletions(-) delete mode 100644 python/ide/impl/intellij.pycharm.community.ide.impl.promotion/resources/promo/backgrounds/promotion_bg.svg create mode 100644 python/ide/impl/resources/backgrounds/promotion_bg.svg create mode 100644 python/ide/impl/resources/icons/close.svg create mode 100644 python/ide/impl/resources/icons/close_dark.svg create mode 100644 python/ide/impl/src/com/intellij/pycharm/community/ide/impl/icons/PycharmCommunityIdeImplIcons.java create mode 100644 python/ide/impl/src/com/intellij/pycharm/community/ide/impl/promo/PyCommunityToUnifiedWelcomeScreenBannerBase.kt create mode 100644 python/ide/impl/src/com/intellij/pycharm/community/ide/impl/promo/WelcomeToUnifiedWelcomeScreenBanner.kt diff --git a/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/resources/promo/backgrounds/promotion_bg.svg b/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/resources/promo/backgrounds/promotion_bg.svg deleted file mode 100644 index ba211d578e33..000000000000 --- a/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/resources/promo/backgrounds/promotion_bg.svg +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/communityToUnified/PyCommunityToUnifiedPromoService.kt b/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/communityToUnified/PyCommunityToUnifiedPromoService.kt index 82c0bc0846d9..194d3b9bafa9 100644 --- a/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/communityToUnified/PyCommunityToUnifiedPromoService.kt +++ b/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/communityToUnified/PyCommunityToUnifiedPromoService.kt @@ -9,6 +9,7 @@ import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.updateSettings.UpdateStrategyCustomization import com.intellij.openapi.updateSettings.impl.* import com.intellij.openapi.util.registry.Registry +import com.intellij.pycharm.community.ide.impl.promo.WelcomeToUnifiedWelcomeScreenBanner import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.sync.Mutex @@ -48,6 +49,8 @@ internal class PyCommunityToUnifiedPromoService(val serviceScope: CoroutineScope val propertiesComponent = PropertiesComponent.getInstance() propertiesComponent.unsetValue(PROMO_UPDATE_DECLINED) propertiesComponent.unsetValue(PROMO_REMIND_ME_LATER_LAST_TIME_CLICKED) + propertiesComponent.unsetValue(WelcomeToUnifiedWelcomeScreenBanner.BANNER_CLOSED_PROPERTY) + Registry.get(REG_PROMO_INTERVAL_SECONDS).resetToDefault() } diff --git a/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/communityToUnified/PyCommunityToUnifiedWelcomeScreenBanner.kt b/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/communityToUnified/PyCommunityToUnifiedWelcomeScreenBanner.kt index 781bc1a6f0f1..5966df278b54 100644 --- a/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/communityToUnified/PyCommunityToUnifiedWelcomeScreenBanner.kt +++ b/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/communityToUnified/PyCommunityToUnifiedWelcomeScreenBanner.kt @@ -4,18 +4,16 @@ package com.intellij.pycharm.community.ide.impl.promotion.communityToUnified import com.intellij.ide.ui.laf.darcula.ui.DarculaButtonUI import com.intellij.openapi.application.EDT import com.intellij.openapi.components.service -import com.intellij.openapi.wm.BannerStartPagePromoter import com.intellij.openapi.wm.StartPagePromoter +import com.intellij.pycharm.community.ide.impl.icons.PycharmCommunityIdeImplIcons +import com.intellij.pycharm.community.ide.impl.promo.PyCommunityToUnifiedWelcomeScreenBannerBase import com.intellij.pycharm.community.ide.impl.promotion.communityToUnified.statistics.PyCommunityUnifiedPromoFusCollector -import com.intellij.pycharm.community.ide.impl.promotion.icons.PycharmCommunityIdeImplPromotionIcons -import com.intellij.ui.components.panels.BackgroundRoundedPanel import com.intellij.ui.components.panels.Wrapper import com.intellij.ui.dsl.builder.EmptySpacingConfiguration import com.intellij.ui.dsl.builder.TopGap import com.intellij.ui.dsl.builder.panel import com.intellij.ui.dsl.gridLayout.UnscaledGaps import com.intellij.ui.dsl.gridLayout.UnscaledGapsY -import com.intellij.util.IconUtil import com.intellij.util.PlatformUtils import com.intellij.util.ui.JBFont import com.intellij.util.ui.JBUI @@ -24,21 +22,16 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.Nls -import java.awt.Graphics -import java.awt.Graphics2D -import java.awt.geom.RoundRectangle2D -import javax.swing.BoxLayout import javax.swing.Icon import javax.swing.JComponent import javax.swing.JPanel -import kotlin.math.min @ApiStatus.Internal -internal class PyCommunityToUnifiedWelcomeScreenBanner : BannerStartPagePromoter() { +internal class PyCommunityToUnifiedWelcomeScreenBanner : PyCommunityToUnifiedWelcomeScreenBannerBase() { override val headerLabel: @Nls String = "" override val actionLabel: @Nls String = "" override val description: @Nls String = "" - override val promoImage: Icon = PycharmCommunityIdeImplPromotionIcons.Backgrounds.Promotion_bg + override val promoImage: Icon = PycharmCommunityIdeImplIcons.Backgrounds.Promotion_bg private val mainBannerPanel = createMainPanel() @@ -58,19 +51,19 @@ internal class PyCommunityToUnifiedWelcomeScreenBanner : BannerStartPagePromoter } } } - return container + return wrapIntoSvgBackgroundPanel(container) } - override fun runAction() {} - override fun canCreatePromo(isEmptyState: Boolean): Boolean { return PlatformUtils.isPyCharmCommunity() } + override fun getMainPanel(): JPanel = container + override fun getPriorityLevel(): Int = StartPagePromoter.PRIORITY_LEVEL_HIGH - private fun createMainPanel(): JPanel { - val panel = panel { + private fun createMainPanel(): JPanel = + panel { customizeSpacingConfiguration(EmptySpacingConfiguration()) { row { label(PyPromoSharedComponents.headerTitle) @@ -101,46 +94,4 @@ internal class PyCommunityToUnifiedWelcomeScreenBanner : BannerStartPagePromoter isOpaque = false border = JBUI.Borders.empty(0, 12, 16, 0) } - return wrapIntoSvgBackgroundPanel(panel, JBUI.scale(16), promoImage) - } - - private fun wrapIntoSvgBackgroundPanel(panel: JPanel, arc: Int, bgImage: Icon): JPanel { - val svgPanel = SvgBackgroundRightAnchoredPanel(arc, bgImage).apply { - layout = BoxLayout(this, BoxLayout.X_AXIS) - } - svgPanel.add(panel) - return svgPanel - } - - private class SvgBackgroundRightAnchoredPanel( - private val arc: Int, - private val bgImage: Icon, - ) : BackgroundRoundedPanel(arc) { - - override fun paintComponent(g: Graphics) { - super.paintComponent(g) - if (width <= 0 || height <= 0) return - - val iw = bgImage.iconWidth - val ih = bgImage.iconHeight - if (iw <= 0 || ih <= 0) return - - val g2 = g.create() as Graphics2D - try { - g2.clip(RoundRectangle2D.Float(0f, 0f, width.toFloat(), height.toFloat(), arc.toFloat(), arc.toFloat())) - val sx = width.toFloat() / iw - val sy = height.toFloat() / ih - val scale = min(sx, sy) - - val scaled = IconUtil.scale(bgImage, this, scale) - // Right-anchored (no padding) - val x = width - scaled.iconWidth - val y = 0 - scaled.paintIcon(this, g2, x, y) - } - finally { - g2.dispose() - } - } - } } \ No newline at end of file diff --git a/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/communityToUnified/PyPromoSharedComponents.kt b/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/communityToUnified/PyPromoSharedComponents.kt index 156c2b63f08a..33406bf2a405 100644 --- a/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/communityToUnified/PyPromoSharedComponents.kt +++ b/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/communityToUnified/PyPromoSharedComponents.kt @@ -9,7 +9,7 @@ import java.awt.Color internal object PyPromoSharedComponents { - val popUpImg = PycharmCommunityIdeImplPromotionIcons.Images.Popup_img + val popUpImg = PycharmCommunityIdeImplPromotionIcons.Popup_img val infoFontColor = JBColor(Color.decode("#6C707E"), Color.decode("#A8ADBD")) // URLs used by promo-related messages diff --git a/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/icons/PycharmCommunityIdeImplPromotionIcons.java b/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/icons/PycharmCommunityIdeImplPromotionIcons.java index a4ad5d26b7fd..370c819993a6 100644 --- a/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/icons/PycharmCommunityIdeImplPromotionIcons.java +++ b/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/icons/PycharmCommunityIdeImplPromotionIcons.java @@ -15,12 +15,5 @@ public final class PycharmCommunityIdeImplPromotionIcons { private static @NotNull Icon load(@NotNull String path, int cacheKey, int flags) { return IconManager.getInstance().loadRasterizedIcon(path, PycharmCommunityIdeImplPromotionIcons.class.getClassLoader(), cacheKey, flags); } - - public static final class Backgrounds { - /** 360x128 */ public static final @NotNull Icon Promotion_bg = load("promo/backgrounds/promotion_bg.svg", -882473096, 0); - } - - public static final class Images { - /** 384x242 */ public static final @NotNull Icon Popup_img = load("promo/images/popup_img.svg", 986597622, 0); - } + /** 384x242 */ public static final @NotNull Icon Popup_img = load("promo/images/popup_img.svg", 986597622, 0); } diff --git a/python/ide/impl/resources/backgrounds/promotion_bg.svg b/python/ide/impl/resources/backgrounds/promotion_bg.svg new file mode 100644 index 000000000000..6795402cbc55 --- /dev/null +++ b/python/ide/impl/resources/backgrounds/promotion_bg.svg @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/ide/impl/resources/icons/close.svg b/python/ide/impl/resources/icons/close.svg new file mode 100644 index 000000000000..4000ed8344b0 --- /dev/null +++ b/python/ide/impl/resources/icons/close.svg @@ -0,0 +1,4 @@ + + + + diff --git a/python/ide/impl/resources/icons/close_dark.svg b/python/ide/impl/resources/icons/close_dark.svg new file mode 100644 index 000000000000..1ded8acf432f --- /dev/null +++ b/python/ide/impl/resources/icons/close_dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/python/ide/impl/resources/intellij.pycharm.community.ide.impl.xml b/python/ide/impl/resources/intellij.pycharm.community.ide.impl.xml index bed3c3844f76..4b6a92a4be5f 100644 --- a/python/ide/impl/resources/intellij.pycharm.community.ide.impl.xml +++ b/python/ide/impl/resources/intellij.pycharm.community.ide.impl.xml @@ -139,6 +139,8 @@ + @@ -163,6 +165,8 @@ + diff --git a/python/ide/impl/resources/messages/PyCharmCommunityCustomizationBundle.properties b/python/ide/impl/resources/messages/PyCharmCommunityCustomizationBundle.properties index 6cc92ba28ac3..378a29e9f4a2 100644 --- a/python/ide/impl/resources/messages/PyCharmCommunityCustomizationBundle.properties +++ b/python/ide/impl/resources/messages/PyCharmCommunityCustomizationBundle.properties @@ -77,11 +77,14 @@ promotion.update.to.unified.banner.header.title=Update to the unified PyCharm promotion.update.to.unified.info.text=Starting with version 2025.3, all updates will be released exclusively for the unified PyCharm.
Update the IDE to keep receiving new features \u2013 all core functionalities will remain free. promotion.update.to.unified.update.now.button.label=Update Now promotion.update.to.unified.learn.more.hyper.link=Learn More -promotion.update.to.unified.closed.banner.go.to.settings.info=You can find and install the new update in the Settings section promotion.update.to.unified.skip.button.label=Skip action.ShowPyCommunityToUnifiedTooltipAction.text=Community To Unified Tooltip action.ShowPyCommunityToUnifiedTooltipAction.description=For testing purposes action.ShowPyCommunityToUnifiedPromoDialogAction.text=Community To Unified Modal Promo action.ShowPyCommunityToUnifiedPromoDialogAction.description=For testing purposes action.ResetCommunityToUnifiedPromoStateAction.text=Reset Community To Unified Promo State -action.ResetCommunityToUnifiedPromoStateAction.description=For testing purposes \ No newline at end of file +action.ResetCommunityToUnifiedPromoStateAction.description=For testing purposes +promotion.welcome.to.unified.main.text=Everything is in one place! Switch between core and advanced
features within the same installation. +promotion.welcome.to.unified.title=Welcome to unified PyCharm! +promotion.welcome.to.unified.close=Close +promotion.welcome.to.unified.close.banner=Close banner \ No newline at end of file diff --git a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/icons/PycharmCommunityIdeImplIcons.java b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/icons/PycharmCommunityIdeImplIcons.java new file mode 100644 index 000000000000..08864ecbaf8e --- /dev/null +++ b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/icons/PycharmCommunityIdeImplIcons.java @@ -0,0 +1,26 @@ +// 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.pycharm.community.ide.impl.icons; + +import com.intellij.ui.IconManager; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; + +/** + * NOTE THIS FILE IS AUTO-GENERATED + * DO NOT EDIT IT BY HAND, run "Generate icon classes" configuration instead + */ +@org.jetbrains.annotations.ApiStatus.Internal +public final class PycharmCommunityIdeImplIcons { + private static @NotNull Icon load(@NotNull String path, int cacheKey, int flags) { + return IconManager.getInstance().loadRasterizedIcon(path, PycharmCommunityIdeImplIcons.class.getClassLoader(), cacheKey, flags); + } + + public static final class Backgrounds { + /** 770x128 */ public static final @NotNull Icon Promotion_bg = load("backgrounds/promotion_bg.svg", -1173415960, 0); + } + + public static final class Icons { + /** 16x16 */ public static final @NotNull Icon Close = load("icons/close.svg", -540869824, 2); + } +} diff --git a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/promo/PyCommunityToUnifiedWelcomeScreenBannerBase.kt b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/promo/PyCommunityToUnifiedWelcomeScreenBannerBase.kt new file mode 100644 index 000000000000..47ee28c8e8cf --- /dev/null +++ b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/promo/PyCommunityToUnifiedWelcomeScreenBannerBase.kt @@ -0,0 +1,93 @@ +// 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.pycharm.community.ide.impl.promo + +import com.intellij.openapi.Disposable +import com.intellij.openapi.observable.util.whenDisposed +import com.intellij.openapi.util.Disposer +import com.intellij.openapi.wm.BannerStartPagePromoter +import com.intellij.openapi.wm.impl.IdeBackgroundUtil +import com.intellij.util.SVGLoader +import com.intellij.util.ui.GraphicsUtil +import com.intellij.util.ui.JBInsets +import com.intellij.util.ui.JBSwingUtilities +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.annotations.Nls +import java.awt.* +import javax.swing.JComponent +import javax.swing.JPanel + +@ApiStatus.Internal +abstract class PyCommunityToUnifiedWelcomeScreenBannerBase : BannerStartPagePromoter() { + override val headerLabel: @Nls String = "" + override val actionLabel: @Nls String = "" + override val description: @Nls String = "" + + abstract fun getMainPanel(): JPanel + + override fun getPromotion(isEmptyState: Boolean): JComponent { + val mainPanel = getMainPanel() + return wrapIntoSvgBackgroundPanel(mainPanel) + } + + override fun runAction() {} + + fun wrapIntoSvgBackgroundPanel(panel: JPanel): JPanel { + val disposable = Disposer.newDisposable("BackgroundPanel") + val url = WelcomeToUnifiedWelcomeScreenBanner::class.java.getResource("/backgrounds/promotion_bg.svg") ?: return panel + val backgroundImage = SVGLoader.load(url, 1f) + val backgroundPanel = BackgroundPanel(BorderLayout(), backgroundImage, disposable) + backgroundPanel.add(panel, BorderLayout.CENTER) + backgroundPanel.isOpaque = false + return backgroundPanel + } + + private class BackgroundPanel(layout: LayoutManager, private val image: Image, parentDisposable: Disposable) : JPanel(layout) { + + private var disposable: Disposable? = null + + init { + parentDisposable.whenDisposed { clearResources() } + installBackground() + } + + override fun paintComponent(g: Graphics) { + super.paintComponent(g) + val g2 = g.create() as Graphics2D + try { + GraphicsUtil.setupAAPainting(g2) + g2.color = background + g2.fillRoundRect(0, 0, width, height, 16, 16) + } + finally { + g2.dispose() + } + } + + override fun getComponentGraphics(g: Graphics?): Graphics? { + return JBSwingUtilities.runGlobalCGTransform(this, super.getComponentGraphics(g)) + } + + override fun updateUI() { + super.updateUI() + @Suppress("SENSELESS_COMPARISON") + if (image != null) { + installBackground() + } + } + + private fun installBackground() { + clearResources() + disposable = Disposer.newDisposable("BackgroundPanel") + IdeBackgroundUtil.createTemporaryBackgroundTransform(this, image, + IdeBackgroundUtil.Fill.SCALE, + IdeBackgroundUtil.Anchor.MIDDLE_RIGHT, + 1f, JBInsets.emptyInsets(), + disposable) + } + + private fun clearResources() { + disposable?.let { Disposer.dispose(it) } + disposable = null + } + } +} \ No newline at end of file diff --git a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/promo/WelcomeToUnifiedWelcomeScreenBanner.kt b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/promo/WelcomeToUnifiedWelcomeScreenBanner.kt new file mode 100644 index 000000000000..457546a62987 --- /dev/null +++ b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/promo/WelcomeToUnifiedWelcomeScreenBanner.kt @@ -0,0 +1,112 @@ +// 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.pycharm.community.ide.impl.promo + + +import com.intellij.ide.AppLifecycleListener +import com.intellij.ide.util.PropertiesComponent +import com.intellij.openapi.actionSystem.ActionUpdateThread +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.application.ApplicationInfo +import com.intellij.openapi.project.DumbAwareAction +import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle +import com.intellij.pycharm.community.ide.impl.icons.PycharmCommunityIdeImplIcons +import com.intellij.ui.JBColor +import com.intellij.ui.components.panels.Wrapper +import com.intellij.ui.dsl.builder.* +import com.intellij.ui.dsl.gridLayout.UnscaledGaps +import com.intellij.ui.dsl.gridLayout.UnscaledGapsY +import com.intellij.util.IconUtil +import com.intellij.util.PlatformUtils +import com.intellij.util.ui.JBFont +import com.intellij.util.ui.JBUI +import java.awt.Color +import java.awt.Dimension +import javax.swing.Icon +import javax.swing.JPanel + +private const val PREV_BUILD_WAS_PC_PROP = "pycharm.previous.version.was.community" + +class WelcomeToUnifiedWelcomeScreenBanner : PyCommunityToUnifiedWelcomeScreenBannerBase() { + + companion object { + const val BANNER_CLOSED_PROPERTY: String = "WELCOME_TO_UNIFIED_BANNER_CLOSED" + } + + private fun isBannerClosed(): Boolean = PropertiesComponent.getInstance().getBoolean(BANNER_CLOSED_PROPERTY) + private fun onBannerClosed() = PropertiesComponent.getInstance().setValue(BANNER_CLOSED_PROPERTY, true) + + override val promoImage: Icon = PycharmCommunityIdeImplIcons.Backgrounds.Promotion_bg + + private val infoFontColor = JBColor(Color.decode("#6C707E"), Color.decode("#A8ADBD")) + private val closeIcon = IconUtil.scale(PycharmCommunityIdeImplIcons.Icons.Close, null, 1.1f) + + private val myCloseAction = + object : DumbAwareAction(PyCharmCommunityCustomizationBundle.message("promotion.welcome.to.unified.close"), + PyCharmCommunityCustomizationBundle.message("promotion.welcome.to.unified.close.banner"), + closeIcon) { + override fun getActionUpdateThread() = ActionUpdateThread.EDT + override fun actionPerformed(e: AnActionEvent) { + onBannerClosed() + container.isVisible = false + container.repaint() + } + } + + val container: Wrapper = Wrapper(createMainPanel()) + + override fun canCreatePromo(isEmptyState: Boolean): Boolean { + val isUnified = ApplicationInfo.getInstance().build.productCode.equals("PY", ignoreCase = true) + return isUnified && !isBannerClosed() && isUpgradeFromCommunityToUnified() + } + + private fun createMainPanel(): JPanel { + val panel = panel { + customizeSpacingConfiguration(EmptySpacingConfiguration()) { + row { + label(PyCharmCommunityCustomizationBundle.message("promotion.welcome.to.unified.title")) + .applyToComponent { + font = JBFont.h3().asBold() + } + actionButton(myCloseAction) + .applyToComponent { + setMinimumButtonSize { Dimension(closeIcon.iconWidth, closeIcon.iconHeight) } + } + .align(AlignX.RIGHT) + .align(AlignY.TOP) + .customize(UnscaledGaps(right = 8)) + }.customize(customRowGaps = UnscaledGapsY(top = 12)) + row { + text(PyCharmCommunityCustomizationBundle.message("promotion.welcome.to.unified.main.text")) + .applyToComponent { + foreground = infoFontColor + } + }.customize(customRowGaps = UnscaledGapsY(top = 4, bottom = 8)) + } + row { + browserLink(PyCharmCommunityCustomizationBundle.message("promotion.update.to.unified.learn.more.hyper.link"), + "https://blog.jetbrains.com/pycharm/2025/04/unified-pycharm/") + }.topGap(TopGap.NONE) + }.apply { + isOpaque = false + border = JBUI.Borders.empty(0, 12, 16, 0) + } + return panel + } + + override fun getMainPanel(): JPanel = container + + private fun isUpgradeFromCommunityToUnified(): Boolean { + val currentCode = ApplicationInfo.getInstance().build.productCode + + val prevWasCommunity = PropertiesComponent.getInstance().getBoolean(PREV_BUILD_WAS_PC_PROP, false) + return prevWasCommunity && currentCode.equals("PY", ignoreCase = true) + } +} + +class CommunityToUnifiedListener : AppLifecycleListener { + override fun appStarted() { + if (PlatformUtils.isPyCharmCommunity()) { + PropertiesComponent.getInstance().setValue(PREV_BUILD_WAS_PC_PROP, true) + } + } +} \ No newline at end of file