diff --git a/platform/core-api/src/com/intellij/ui/LayeredIcon.java b/platform/core-api/src/com/intellij/ui/LayeredIcon.java index 1e73d2cc615e..7e6d84fc18e4 100644 --- a/platform/core-api/src/com/intellij/ui/LayeredIcon.java +++ b/platform/core-api/src/com/intellij/ui/LayeredIcon.java @@ -54,8 +54,8 @@ public class LayeredIcon extends JBUI.AuxScalableJBIcon { } @Override - public LayeredIcon withPreScaled(boolean preScaled) { - super.withPreScaled(preScaled); + public LayeredIcon withJBUIPreScaled(boolean preScaled) { + super.withJBUIPreScaled(preScaled); for (int i = 0; i < myIcons.length; i++) { myHShifts[i] = scaleVal(myHShifts[i], Scale.JBUI); myVShifts[i] = scaleVal(myVShifts[i], Scale.JBUI); @@ -271,11 +271,10 @@ public class LayeredIcon extends JBUI.AuxScalableJBIcon { @Override public Icon scale(float scale) { - if (getScale() != scale) { - super.scale(scale); - if (myScaledIcons!= null) Arrays.fill(myScaledIcons, null); - updateSize(); - } + if (scale == 1f) return this; + + setScale(scaleVal(scale, Scale.ARBITRARY)); + if (myScaledIcons != null) Arrays.fill(myScaledIcons, null); return this; } } diff --git a/platform/core-api/src/com/intellij/ui/RowIcon.java b/platform/core-api/src/com/intellij/ui/RowIcon.java index 0d9ebe1ca83c..173d3b847f3f 100644 --- a/platform/core-api/src/com/intellij/ui/RowIcon.java +++ b/platform/core-api/src/com/intellij/ui/RowIcon.java @@ -57,10 +57,10 @@ public class RowIcon extends JBUI.AuxScalableJBIcon { @Override public Icon scale(float scale) { - if (getScale() != scale || (getScale() != 1f && myScaledIcons == null)) { - super.scale(scale); - rescale(); - } + if (scale == 1f) return this; + + setScale(scaleVal(scale, Scale.ARBITRARY)); + rescale(); return this; } diff --git a/platform/platform-api/src/com/intellij/ui/SizedIcon.java b/platform/platform-api/src/com/intellij/ui/SizedIcon.java index 9547ab1c6a59..3d8e176a983b 100644 --- a/platform/platform-api/src/com/intellij/ui/SizedIcon.java +++ b/platform/platform-api/src/com/intellij/ui/SizedIcon.java @@ -58,9 +58,12 @@ public class SizedIcon extends JBUI.ScalableJBIcon { @Override public Icon scale(float scale) { + if (scale == 1f) return this; + if (myDelegate instanceof ScalableIcon) { - myScaledDelegate = ((ScalableIcon)myDelegate).scale(scale); + setScale(scaleVal(scale, Scale.ARBITRARY)); + myScaledDelegate = ((ScalableIcon)myDelegate).scale(getScale()); } - return super.scale(scale); + return this; } } diff --git a/platform/util/src/com/intellij/openapi/util/IconLoader.java b/platform/util/src/com/intellij/openapi/util/IconLoader.java index 40ac1fa8e905..2097900d4b02 100644 --- a/platform/util/src/com/intellij/openapi/util/IconLoader.java +++ b/platform/util/src/com/intellij/openapi/util/IconLoader.java @@ -457,6 +457,8 @@ public final class IconLoader { @Override public Icon scale(float scale) { + if (scale == 1f) return this; + if (!isValid()) getRealIcon(); // force state update & cache reset Icon icon = myScaledIconsCache.getScaledIcon(scale); @@ -578,10 +580,12 @@ public final class IconLoader { @Override public Icon scale(float scale) { + if (scale == 1f) return this; + getOrComputeIcon(); if (myIcon instanceof ScalableIcon) { - myIcon = ((ScalableIcon)myIcon).scale(scale); - return super.scale(scale); + setScale(scaleVal(scale, Scale.ARBITRARY)); + myIcon = ((ScalableIcon)myIcon).scale(getScale()); } return this; } diff --git a/platform/util/src/com/intellij/openapi/util/ScalableIcon.java b/platform/util/src/com/intellij/openapi/util/ScalableIcon.java index 93eea6d40c65..86e1e8ce53cb 100644 --- a/platform/util/src/com/intellij/openapi/util/ScalableIcon.java +++ b/platform/util/src/com/intellij/openapi/util/ScalableIcon.java @@ -23,6 +23,11 @@ import javax.swing.*; */ public interface ScalableIcon extends Icon { /** + * The method does either of the following: + * - creates/retrieves a scaled version of this icon and returns it. + * - scales this icon and returns it. + * The exact behavior is implementation dependant. + * * @param scaleFactor scale * @return scaled icon with width getIconWidth() * scaleFactor and height getIconHeight() * scaleFactor */ diff --git a/platform/util/src/com/intellij/util/ui/ColorIcon.java b/platform/util/src/com/intellij/util/ui/ColorIcon.java index 48f15a49a66d..a93581264859 100644 --- a/platform/util/src/com/intellij/util/ui/ColorIcon.java +++ b/platform/util/src/com/intellij/util/ui/ColorIcon.java @@ -43,6 +43,18 @@ public class ColorIcon extends EmptyIcon { this(size, color, false); } + protected ColorIcon(ColorIcon icon) { + super(icon); + myColor = icon.myColor; + myBorder = icon.myBorder; + myColorSize = icon.myColorSize; + } + + @Override + protected ColorIcon copy() { + return new ColorIcon(this); + } + public Color getIconColor() { return myColor; } diff --git a/platform/util/src/com/intellij/util/ui/EmptyIcon.java b/platform/util/src/com/intellij/util/ui/EmptyIcon.java index 211127b5b86c..29aaa9dd28fd 100644 --- a/platform/util/src/com/intellij/util/ui/EmptyIcon.java +++ b/platform/util/src/com/intellij/util/ui/EmptyIcon.java @@ -16,7 +16,6 @@ package com.intellij.util.ui; -import com.intellij.openapi.util.Pair; import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -32,9 +31,8 @@ import java.util.Map; * * @see ColorIcon */ -public class EmptyIcon extends JBUI.ScalableJBIcon { - private static final Map, EmptyIcon> cache = - new HashMap, EmptyIcon>(); // (size, preScaled) -> (icon) +public class EmptyIcon extends JBUI.CachingScalableJBIcon { + private static final Map cache = new HashMap(); public static final Icon ICON_16 = JBUI.scale(create(16)); public static final Icon ICON_18 = JBUI.scale(create(18)); @@ -43,13 +41,12 @@ public class EmptyIcon extends JBUI.ScalableJBIcon { protected final int width; protected final int height; - private EmptyIcon myScaledCache; private boolean myUseCache; /** * Creates an icon of the provided size. * - * Use {@link JBUI#scale(EmptyIcon)} to meet HiDPI. + * Use {@link JBUI#scale(JBUI.JBIcon)} to meet HiDPI. */ public static EmptyIcon create(int size) { return create(size, size); @@ -58,7 +55,7 @@ public class EmptyIcon extends JBUI.ScalableJBIcon { /** * Creates an icon of the provided size. * - * Use {@link JBUI#scale(EmptyIcon)} to meet HiDPI. + * Use {@link JBUI#scale(JBUI.JBIcon)} to meet HiDPI. */ public static EmptyIcon create(int width, int height) { return create(width, height, true); @@ -98,39 +95,34 @@ public class EmptyIcon extends JBUI.ScalableJBIcon { myUseCache = icon.myUseCache; } + @Override protected EmptyIcon copy() { return new EmptyIcon(this); } @Override - public EmptyIcon withPreScaled(boolean preScaled) { - if (myUseCache && isPreScaled() != preScaled) { - Pair key = key(width, height, preScaled); + public EmptyIcon withJBUIPreScaled(boolean preScaled) { + if (myUseCache && isJBUIPreScaled() != preScaled) { + Integer key = key(width, height, isJBUIPreScaled()); if (key != null) cache.remove(key); // rather useless to keep it in cache return create(width, height, preScaled); } - return (EmptyIcon)super.withPreScaled(preScaled); + return (EmptyIcon)super.withJBUIPreScaled(preScaled); } private static EmptyIcon create(int width, int height, boolean preScaled) { - Pair key = key(width, height, preScaled); + Integer key = key(width, height, preScaled); EmptyIcon icon = (key != null) ? cache.get(key) : null; if (icon == null) { icon = new EmptyIcon(width, height, true); - icon.setPreScaled(preScaled); + icon.setJBUIPreScaled(preScaled); if (key != null) cache.put(key, icon); } return icon; } - private static Pair key(int width, int height, boolean preScaled) { - return (width == height && width < 129) ? Pair.create(width, preScaled) : null; - } - - private boolean isCached() { - if (!myUseCache) return false; - Pair key = key(width, height, isPreScaled()); - return key != null && cache.containsKey(key); + private static Integer key(int width, int height, boolean preScaled) { + return (width == height && width < 129) ? preScaled ? width : JBUI.scale(width) : null; } @Override @@ -154,20 +146,18 @@ public class EmptyIcon extends JBUI.ScalableJBIcon { final EmptyIcon icon = (EmptyIcon)o; - if (height != icon.height) return false; - if (width != icon.width) return false; + if (scaleVal(height, Scale.JBUI) != icon.scaleVal(height, Scale.JBUI)) return false; + if (scaleVal(width, Scale.JBUI) != icon.scaleVal(width, Scale.JBUI)) return false; if (getScale() != icon.getScale()) return false; - if (isPreScaled() != icon.isPreScaled()) return false; return true; } @Override public int hashCode() { - int result = width; - result = 31 * result + height; + int result = scaleVal(width, Scale.JBUI); + result = 31 * result + scaleVal(height, Scale.JBUI); result = 31 * result + (getScale() != +0.0f ? Float.floatToIntBits(getScale()) : 0); - result = 31 * result + Boolean.valueOf(isPreScaled()).hashCode(); return result; } @@ -175,17 +165,6 @@ public class EmptyIcon extends JBUI.ScalableJBIcon { return new EmptyIconUIResource(this); } - @Override - public EmptyIcon scale(float scale) { - if (getScale() == scale || !isCached()) { - return (EmptyIcon)super.scale(scale); - } - if (myScaledCache == null || myScaledCache.getScale() != scale) { - myScaledCache = copy(); // never cached - } - return myScaledCache.scale(scale); - } - public static class EmptyIconUIResource extends EmptyIcon implements UIResource { protected EmptyIconUIResource(EmptyIcon icon) { super(icon); diff --git a/platform/util/src/com/intellij/util/ui/JBUI.java b/platform/util/src/com/intellij/util/ui/JBUI.java index e5ef636030ad..84b183b30221 100644 --- a/platform/util/src/com/intellij/util/ui/JBUI.java +++ b/platform/util/src/com/intellij/util/ui/JBUI.java @@ -166,7 +166,7 @@ public class JBUI { } public static T scale(T icon) { - return (T)icon.withPreScaled(false); + return (T)icon.withJBUIPreScaled(false); } public static JBDimension emptySize() { @@ -309,14 +309,14 @@ public class JBUI { /** * @return whether the icon size metrics are pre-scaled or not */ - protected boolean isPreScaled() { + protected boolean isJBUIPreScaled() { return myInitialJBUIScale != 1f; } /** * Sets the icon size metrics to {@code preScaled} */ - protected void setPreScaled(boolean preScaled) { + protected void setJBUIPreScaled(boolean preScaled) { myInitialJBUIScale = preScaled ? scale(1f) : 1f; } @@ -325,8 +325,8 @@ public class JBUI { * * @return the icon (this or new instance) with size metrics set to {@code preScaled} */ - public JBIcon withPreScaled(boolean preScaled) { - setPreScaled(preScaled); + public JBIcon withJBUIPreScaled(boolean preScaled) { + setJBUIPreScaled(preScaled); return this; } @@ -399,12 +399,44 @@ public class JBUI { return super.scaleVal(value * myScale); } } + } + /** + * A ScalableJBIcon providing an immutable caching implementation of the {@link #scale(float)} method. + * + * @author tav + * @author Aleksey Pivovarov + */ + public static abstract class CachingScalableJBIcon extends ScalableJBIcon { + private CachingScalableJBIcon myScaledIconCache; + + protected CachingScalableJBIcon() {} + + protected CachingScalableJBIcon(CachingScalableJBIcon icon) { + super(icon); + myScaledIconCache = null; + } + + /** + * @return a new scaled copy of this icon, or the cached instance of the provided scale + */ @Override public Icon scale(float scale) { - if (getScale() != scale) setScale(scale); - return this; + if (scale == 1f) return this; + + scale = scaleVal(scale, Scale.ARBITRARY); // accumulate scale + if (myScaledIconCache == null || myScaledIconCache.getScale() != scale) { + myScaledIconCache = copy(); + myScaledIconCache.setScale(scale); + } + return myScaledIconCache; } + + /** + * @return a deep copy of this icon instance + */ + @NotNull + protected abstract T copy(); } public interface AuxJBUIScale { diff --git a/platform/util/src/com/intellij/util/ui/TwoColorsIcon.java b/platform/util/src/com/intellij/util/ui/TwoColorsIcon.java index d1e3dd516f38..ca5ff1bc708e 100644 --- a/platform/util/src/com/intellij/util/ui/TwoColorsIcon.java +++ b/platform/util/src/com/intellij/util/ui/TwoColorsIcon.java @@ -51,6 +51,16 @@ public class TwoColorsIcon extends ColorIcon { mySecondColor = secondColor != null ? secondColor : Gray.TRANSPARENT; } + protected TwoColorsIcon(TwoColorsIcon icon) { + super(icon); + mySecondColor = icon.mySecondColor; + } + + @Override + protected TwoColorsIcon copy() { + return new TwoColorsIcon(this); + } + @Override public void paintIcon(final Component component, Graphics g, int x, int y) { Graphics2D g2d = (Graphics2D)g.create(); diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/StructureFilterPopupComponent.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/StructureFilterPopupComponent.java index 3682f1eb6b65..7ab4212cf0ab 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/StructureFilterPopupComponent.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/StructureFilterPopupComponent.java @@ -331,9 +331,9 @@ class StructureFilterPopupComponent extends FilterPopupComponent