PY-82344 Implement Welcome to unified PyCharm! promo welcome screen banner, refactoring

(cherry picked from commit b80df8b40facfbf114d0d8965756768cb799f198)

IJ-CR-179688

GitOrigin-RevId: 06d66ae94cfc38b07ba27e8ea71e7f52d072e19d
This commit is contained in:
Daniil Kalinin
2025-10-23 14:09:28 +00:00
committed by intellij-monorepo-bot
parent d82a65e259
commit ee44190f3c
13 changed files with 375 additions and 172 deletions
@@ -1,103 +0,0 @@
<svg width="360" height="128" viewBox="0 0 360 128" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M84.4762 70.0674L268.763 -187.318C293.214 -221.468 340.85 -231.027 375.159 -208.669L633.748 -40.1578C668.058 -17.7996 676.049 28.0093 651.598 62.1593L467.311 319.544C442.86 353.694 395.225 363.254 360.915 340.895L102.326 172.384C68.0167 150.026 60.0249 104.217 84.4762 70.0674Z"
fill="url(#paint0_linear_232_30135)"/>
<path
d="M138 128.822L248.148 -99.1917C262.763 -129.445 299.476 -143.451 330.148 -130.477L561.327 -32.6895C592 -19.715 605.018 15.3277 590.403 45.5806L480.255 273.594C465.64 303.847 428.928 317.854 398.255 304.879L167.076 207.092C136.403 194.117 123.386 159.075 138 128.822Z"
fill="url(#paint1_linear_232_30135)"/>
<path
d="M189.304 167.896L244.41 -26.8865C251.721 -52.7302 279.055 -68.7618 305.462 -62.6941L504.49 -16.9618C530.897 -10.8941 546.377 14.9753 539.065 40.819L483.959 235.602C476.648 261.446 449.314 277.477 422.907 271.409L223.879 225.677C197.472 219.609 181.993 193.74 189.304 167.896Z"
fill="url(#paint2_linear_232_30135)"/>
<path
d="M235.986 191.748L251.88 30.648C253.989 9.27306 273.53 -7.08205 295.526 -5.88217L461.31 3.16118C483.307 4.36106 499.429 22.6616 497.32 44.0365L481.427 205.137C479.318 226.512 459.777 242.867 437.78 241.667L271.996 232.624C250 231.424 233.878 213.123 235.986 191.748Z"
fill="url(#paint3_linear_232_30135)"/>
<path
d="M276.768 204.22L266.175 75.0081C264.769 57.8642 278.031 42.3122 295.796 40.2718L429.687 24.8938C447.452 22.8534 462.992 35.0973 464.398 52.2413L474.991 181.453C476.397 198.597 463.135 214.149 445.37 216.189L311.479 231.567C293.715 233.608 278.174 221.364 276.768 204.22Z"
fill="url(#paint4_linear_232_30135)"/>
<path
d="M311.188 208.518L284.025 108.052C280.421 94.7222 288.777 80.656 302.689 76.6347L407.541 46.3269C421.454 42.3056 435.653 49.8519 439.257 63.1821L466.421 163.648C470.025 176.978 461.668 191.044 447.756 195.066L342.904 225.374C328.992 229.395 314.792 221.849 311.188 208.518Z"
fill="url(#paint5_linear_232_30135)"/>
<path
d="M339.336 207.234L303.079 131.687C298.269 121.664 302.917 109.436 313.461 104.376L392.932 66.2357C403.476 61.1754 415.923 65.1987 420.733 75.2221L456.99 150.769C461.801 160.792 457.153 173.02 446.609 178.081L367.138 216.221C356.594 221.281 344.147 217.258 339.336 207.234Z"
fill="url(#paint6_linear_232_30135)"/>
<path
d="M361.661 202.399L321.737 147.741C316.44 140.489 318.388 130.215 326.088 124.792L384.126 83.9218C391.827 78.4991 402.363 79.9821 407.661 87.2341L447.585 141.892C452.882 149.144 450.933 159.419 443.233 164.842L385.195 205.712C377.494 211.134 366.958 209.651 361.661 202.399Z"
fill="url(#paint7_linear_232_30135)"/>
<path
d="M378.816 195.559L338.981 157.874C333.696 152.873 333.768 144.505 339.142 139.183L379.644 99.0696C385.018 93.7472 393.66 93.4861 398.945 98.4865L438.78 136.172C444.066 141.172 443.994 149.541 438.62 154.863L398.117 194.976C392.743 200.298 384.102 200.56 378.816 195.559Z"
fill="url(#paint8_linear_232_30135)"/>
<path
d="M391.546 187.833L354.237 163.52C349.286 160.294 348.133 153.685 351.661 148.758L378.25 111.622C381.778 106.695 388.651 105.316 393.601 108.542L430.91 132.854C435.86 136.08 437.013 142.69 433.486 147.617L406.897 184.752C403.369 189.68 396.496 191.059 391.546 187.833Z"
fill="url(#paint9_linear_232_30135)"/>
<path
d="M400.607 180.009L367.253 165.9C362.828 164.028 360.95 158.972 363.058 154.608L378.95 121.71C381.059 117.345 386.356 115.324 390.781 117.196L424.135 131.305C428.561 133.177 430.439 138.233 428.33 142.598L412.438 175.495C410.33 179.86 405.033 181.881 400.607 180.009Z"
fill="url(#paint10_linear_232_30135)"/>
<defs>
<linearGradient id="paint0_linear_232_30135" x1="502.466" y1="-121.657" x2="250.469" y2="265.044" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0EB18" stop-opacity="0.9"/>
<stop offset="0.3" stop-color="#00D886" stop-opacity="0.5"/>
<stop offset="0.59" stop-color="#00C4F4" stop-opacity="0.3"/>
<stop offset="1" stop-color="#00C4F4" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint1_linear_232_30135" x1="444.547" y1="-79.1399" x2="300.748" y2="260.813" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0EB18" stop-opacity="0.9"/>
<stop offset="0.3" stop-color="#00D886" stop-opacity="0.5"/>
<stop offset="0.59" stop-color="#00C4F4" stop-opacity="0.3"/>
<stop offset="1" stop-color="#00C4F4" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint2_linear_232_30135" x1="404.377" y1="-37.7393" x2="338.298" y2="249.837" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0EB18" stop-opacity="0.9"/>
<stop offset="0.3" stop-color="#00D886" stop-opacity="0.5"/>
<stop offset="0.59" stop-color="#00C4F4" stop-opacity="0.3"/>
<stop offset="1" stop-color="#00C4F4" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint3_linear_232_30135" x1="378.241" y1="0.368083" x2="365.385" y2="236.054" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0EB18" stop-opacity="0.9"/>
<stop offset="0.3" stop-color="#00D886" stop-opacity="0.5"/>
<stop offset="0.59" stop-color="#00C4F4" stop-opacity="0.3"/>
<stop offset="1" stop-color="#00C4F4" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint4_linear_232_30135" x1="362.85" y1="33.9701" x2="384.429" y2="221.849" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0EB18" stop-opacity="0.9"/>
<stop offset="0.3" stop-color="#00D886" stop-opacity="0.5"/>
<stop offset="0.59" stop-color="#00C4F4" stop-opacity="0.3"/>
<stop offset="1" stop-color="#00C4F4" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint5_linear_232_30135" x1="355.403" y1="62.5602" x2="397.575" y2="208.458" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0EB18" stop-opacity="0.9"/>
<stop offset="0.3" stop-color="#00D886" stop-opacity="0.5"/>
<stop offset="0.59" stop-color="#00C4F4" stop-opacity="0.3"/>
<stop offset="1" stop-color="#00C4F4" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint6_linear_232_30135" x1="353.582" y1="86.118" x2="406.498" y2="196.376" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0EB18" stop-opacity="0.9"/>
<stop offset="0.3" stop-color="#00D886" stop-opacity="0.5"/>
<stop offset="0.59" stop-color="#00C4F4" stop-opacity="0.3"/>
<stop offset="1" stop-color="#00C4F4" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint7_linear_232_30135" x1="355.534" y1="104.945" x2="412.402" y2="185.702" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0EB18" stop-opacity="0.9"/>
<stop offset="0.3" stop-color="#00D886" stop-opacity="0.5"/>
<stop offset="0.59" stop-color="#00C4F4" stop-opacity="0.3"/>
<stop offset="1" stop-color="#00C4F4" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint8_linear_232_30135" x1="359.819" y1="119.532" x2="416.107" y2="176.367" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0EB18" stop-opacity="0.9"/>
<stop offset="0.3" stop-color="#00D886" stop-opacity="0.5"/>
<stop offset="0.59" stop-color="#00C4F4" stop-opacity="0.3"/>
<stop offset="1" stop-color="#00C4F4" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint9_linear_232_30135" x1="365.355" y1="130.452" x2="418.148" y2="168.252" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0EB18" stop-opacity="0.9"/>
<stop offset="0.3" stop-color="#00D886" stop-opacity="0.5"/>
<stop offset="0.59" stop-color="#00C4F4" stop-opacity="0.3"/>
<stop offset="1" stop-color="#00C4F4" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint10_linear_232_30135" x1="371.362" y1="138.312" x2="418.895" y2="161.274" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0EB18" stop-opacity="0.9"/>
<stop offset="0.3" stop-color="#00D886" stop-opacity="0.5"/>
<stop offset="0.59" stop-color="#00C4F4" stop-opacity="0.3"/>
<stop offset="1" stop-color="#00C4F4" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 8.0 KiB

@@ -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()
}
@@ -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()
}
}
}
}
@@ -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
@@ -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);
}
@@ -0,0 +1,113 @@
<svg width="770" height="128" viewBox="0 0 770 128" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_664_32769)">
<g clip-path="url(#clip1_664_32769)">
<path
d="M494.476 70.0674L678.763 -187.318C703.214 -221.468 750.85 -231.027 785.159 -208.669L1043.75 -40.1578C1078.06 -17.7996 1086.05 28.0092 1061.6 62.1592L877.311 319.544C852.86 353.694 805.225 363.253 770.915 340.895L512.326 172.384C478.017 150.026 470.025 104.217 494.476 70.0674Z"
fill="url(#paint0_linear_664_32769)"/>
<path
d="M548 128.822L658.148 -99.1917C672.763 -129.445 709.476 -143.452 740.148 -130.477L971.327 -32.6895C1002 -19.715 1015.02 15.3277 1000.4 45.5806L890.255 273.594C875.64 303.847 838.928 317.854 808.255 304.879L577.076 207.092C546.403 194.117 533.386 159.075 548 128.822Z"
fill="url(#paint1_linear_664_32769)"/>
<path
d="M599.304 167.896L654.41 -26.8865C661.721 -52.7302 689.055 -68.7618 715.462 -62.6941L914.49 -16.9618C940.897 -10.8941 956.377 14.9753 949.065 40.819L893.959 235.602C886.648 261.446 859.314 277.477 832.907 271.409L633.879 225.677C607.472 219.609 591.993 193.74 599.304 167.896Z"
fill="url(#paint2_linear_664_32769)"/>
<path
d="M645.986 191.748L661.88 30.648C663.989 9.27305 683.53 -7.08207 705.526 -5.88219L871.31 3.16116C893.307 4.36104 909.429 22.6615 907.32 44.0365L891.427 205.137C889.318 226.512 869.777 242.867 847.78 241.667L681.996 232.624C660 231.424 643.878 213.123 645.986 191.748Z"
fill="url(#paint3_linear_664_32769)"/>
<path
d="M686.768 204.22L676.175 75.0081C674.769 57.8642 688.031 42.3122 705.796 40.2718L839.687 24.8938C857.452 22.8534 872.992 35.0973 874.398 52.2412L884.991 181.453C886.397 198.597 873.135 214.149 855.37 216.189L721.479 231.567C703.715 233.608 688.174 221.364 686.768 204.22Z"
fill="url(#paint4_linear_664_32769)"/>
<path
d="M721.188 208.518L694.025 108.052C690.421 94.7222 698.777 80.656 712.689 76.6347L817.541 46.3269C831.454 42.3056 845.653 49.8519 849.257 63.1821L876.421 163.648C880.025 176.978 871.668 191.044 857.756 195.066L752.904 225.374C738.992 229.395 724.792 221.849 721.188 208.518Z"
fill="url(#paint5_linear_664_32769)"/>
<path
d="M749.336 207.234L713.079 131.687C708.269 121.664 712.917 109.436 723.461 104.376L802.932 66.2357C813.476 61.1753 825.923 65.1987 830.733 75.2221L866.99 150.769C871.801 160.792 867.153 173.02 856.609 178.081L777.138 216.221C766.594 221.281 754.147 217.258 749.336 207.234Z"
fill="url(#paint6_linear_664_32769)"/>
<path
d="M771.661 202.399L731.737 147.741C726.44 140.489 728.388 130.215 736.088 124.792L794.126 83.9217C801.827 78.4991 812.363 79.9821 817.661 87.2341L857.585 141.892C862.882 149.144 860.933 159.419 853.233 164.841L795.195 205.712C787.494 211.134 776.958 209.651 771.661 202.399Z"
fill="url(#paint7_linear_664_32769)"/>
<path
d="M788.816 195.559L748.981 157.874C743.696 152.873 743.768 144.505 749.142 139.183L789.644 99.0696C795.018 93.7472 803.66 93.4861 808.945 98.4865L848.78 136.172C854.066 141.172 853.994 149.541 848.62 154.863L808.117 194.976C802.743 200.298 794.102 200.56 788.816 195.559Z"
fill="url(#paint8_linear_664_32769)"/>
<path
d="M801.546 187.833L764.237 163.52C759.286 160.294 758.133 153.685 761.661 148.758L788.25 111.622C791.778 106.695 798.651 105.316 803.601 108.542L840.91 132.854C845.86 136.08 847.013 142.69 843.486 147.617L816.897 184.752C813.369 189.68 806.496 191.059 801.546 187.833Z"
fill="url(#paint9_linear_664_32769)"/>
<path
d="M810.607 180.009L777.253 165.9C772.828 164.028 770.95 158.972 773.058 154.608L788.95 121.71C791.059 117.345 796.356 115.324 800.781 117.196L834.135 131.305C838.561 133.177 840.439 138.233 838.33 142.598L822.438 175.495C820.33 179.86 815.033 181.881 810.607 180.009Z"
fill="url(#paint10_linear_664_32769)"/>
</g>
</g>
<defs>
<linearGradient id="paint0_linear_664_32769" x1="912.466" y1="-121.657" x2="660.469" y2="265.044" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0EB18" stop-opacity="0.9"/>
<stop offset="0.3" stop-color="#00D886" stop-opacity="0.5"/>
<stop offset="0.59" stop-color="#00C4F4" stop-opacity="0.3"/>
<stop offset="1" stop-color="#00C4F4" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint1_linear_664_32769" x1="854.547" y1="-79.1399" x2="710.748" y2="260.813" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0EB18" stop-opacity="0.9"/>
<stop offset="0.3" stop-color="#00D886" stop-opacity="0.5"/>
<stop offset="0.59" stop-color="#00C4F4" stop-opacity="0.3"/>
<stop offset="1" stop-color="#00C4F4" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint2_linear_664_32769" x1="814.377" y1="-37.7393" x2="748.298" y2="249.837" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0EB18" stop-opacity="0.9"/>
<stop offset="0.3" stop-color="#00D886" stop-opacity="0.5"/>
<stop offset="0.59" stop-color="#00C4F4" stop-opacity="0.3"/>
<stop offset="1" stop-color="#00C4F4" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint3_linear_664_32769" x1="788.241" y1="0.368068" x2="775.385" y2="236.054" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0EB18" stop-opacity="0.9"/>
<stop offset="0.3" stop-color="#00D886" stop-opacity="0.5"/>
<stop offset="0.59" stop-color="#00C4F4" stop-opacity="0.3"/>
<stop offset="1" stop-color="#00C4F4" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint4_linear_664_32769" x1="772.85" y1="33.9701" x2="794.429" y2="221.849" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0EB18" stop-opacity="0.9"/>
<stop offset="0.3" stop-color="#00D886" stop-opacity="0.5"/>
<stop offset="0.59" stop-color="#00C4F4" stop-opacity="0.3"/>
<stop offset="1" stop-color="#00C4F4" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint5_linear_664_32769" x1="765.403" y1="62.5602" x2="807.575" y2="208.458" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0EB18" stop-opacity="0.9"/>
<stop offset="0.3" stop-color="#00D886" stop-opacity="0.5"/>
<stop offset="0.59" stop-color="#00C4F4" stop-opacity="0.3"/>
<stop offset="1" stop-color="#00C4F4" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint6_linear_664_32769" x1="763.582" y1="86.118" x2="816.498" y2="196.376" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0EB18" stop-opacity="0.9"/>
<stop offset="0.3" stop-color="#00D886" stop-opacity="0.5"/>
<stop offset="0.59" stop-color="#00C4F4" stop-opacity="0.3"/>
<stop offset="1" stop-color="#00C4F4" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint7_linear_664_32769" x1="765.534" y1="104.945" x2="822.402" y2="185.702" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0EB18" stop-opacity="0.9"/>
<stop offset="0.3" stop-color="#00D886" stop-opacity="0.5"/>
<stop offset="0.59" stop-color="#00C4F4" stop-opacity="0.3"/>
<stop offset="1" stop-color="#00C4F4" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint8_linear_664_32769" x1="769.819" y1="119.532" x2="826.107" y2="176.367" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0EB18" stop-opacity="0.9"/>
<stop offset="0.3" stop-color="#00D886" stop-opacity="0.5"/>
<stop offset="0.59" stop-color="#00C4F4" stop-opacity="0.3"/>
<stop offset="1" stop-color="#00C4F4" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint9_linear_664_32769" x1="775.355" y1="130.452" x2="828.148" y2="168.252" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0EB18" stop-opacity="0.9"/>
<stop offset="0.3" stop-color="#00D886" stop-opacity="0.5"/>
<stop offset="0.59" stop-color="#00C4F4" stop-opacity="0.3"/>
<stop offset="1" stop-color="#00C4F4" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint10_linear_664_32769" x1="781.362" y1="138.312" x2="828.895" y2="161.274" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0EB18" stop-opacity="0.9"/>
<stop offset="0.3" stop-color="#00D886" stop-opacity="0.5"/>
<stop offset="0.59" stop-color="#00C4F4" stop-opacity="0.3"/>
<stop offset="1" stop-color="#00C4F4" stop-opacity="0"/>
</linearGradient>
<clipPath id="clip0_664_32769">
<rect width="770" height="128" fill="white"/>
</clipPath>
<clipPath id="clip1_664_32769">
<rect width="360" height="217" fill="white" transform="matrix(1 -8.74228e-08 -8.74228e-08 -1 410 196)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 8.5 KiB

@@ -0,0 +1,4 @@
<!-- Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.5 12.5L12.5 3.5M12.5 12.5L3.5 3.5" stroke="#6C707E" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 321 B

@@ -0,0 +1,4 @@
<!-- Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.5 12.5L12.5 3.5M12.5 12.5L3.5 3.5" stroke="#FFFFFF" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 321 B

@@ -139,6 +139,8 @@
<codeInsight.codeVision.settings.defaults implementation="com.intellij.pycharm.community.ide.impl.PyCharmCodeVisionSettingsDefaults"/>
<statistics.counterUsagesCollector implementationClass="com.intellij.pycharm.community.ide.impl.configuration.ui.PyAddNewEnvCollector"/>
<startPagePromoter
implementation="com.intellij.pycharm.community.ide.impl.promo.WelcomeToUnifiedWelcomeScreenBanner"/>
</extensions>
<extensions defaultExtensionNs="Pythonid">
@@ -163,6 +165,8 @@
<applicationListeners>
<listener class="com.intellij.pycharm.community.ide.impl.welcomeScreen.PyWelcomeScreenNewUsersTracker"
topic="com.intellij.ide.AppLifecycleListener"/>
<listener class="com.intellij.pycharm.community.ide.impl.promo.CommunityToUnifiedListener"
topic="com.intellij.ide.AppLifecycleListener"/>
</applicationListeners>
<actions resource-bundle="messages.ActionsBundle">
@@ -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.<br>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
action.ResetCommunityToUnifiedPromoStateAction.description=For testing purposes
promotion.welcome.to.unified.main.text=Everything is in one place! Switch between core and advanced<br>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
@@ -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);
}
}
@@ -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
}
}
}
@@ -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)
}
}
}