[followup] Add/refactor JBUI.JBIcon/ScalableJBIcon/ValidatingScalableJBIcon

This commit is contained in:
Anton Tarasov
2016-10-31 11:28:44 +03:00
parent 9a32e87755
commit f35438eef6
9 changed files with 237 additions and 120 deletions
@@ -17,6 +17,7 @@ package com.intellij.ui;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.ScalableIcon;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ui.JBUI;
import org.intellij.lang.annotations.MagicConstant;
import org.jetbrains.annotations.NotNull;
@@ -53,6 +54,46 @@ public class LayeredIcon extends JBUI.AuxScalableJBIcon {
}
}
protected LayeredIcon(LayeredIcon icon) {
super(icon);
myIcons = ArrayUtil.copyOf(icon.myIcons);
myScaledIcons = null;
myDisabledLayers = ArrayUtil.copyOf(icon.myDisabledLayers);
myHShifts = ArrayUtil.copyOf(icon.myHShifts);
myVShifts = ArrayUtil.copyOf(icon.myVShifts);
myXShift = icon.myXShift;
myYShift = icon.myYShift;
myWidth = icon.myWidth;
myHeight = icon.myHeight;
}
@Override
protected LayeredIcon copy() {
return new LayeredIcon(this);
}
private Icon[] myScaledIcons() {
if (myScaledIcons != null) {
return myScaledIcons;
}
if (getScale() == 1f) {
return myScaledIcons = myIcons;
}
for (Icon icon : myIcons) {
if (icon != null && !(icon instanceof ScalableIcon)) {
setScale(1f);
return myScaledIcons = myIcons;
}
}
myScaledIcons = new Icon[myIcons.length];
for (int i = 0; i < myIcons.length; i++) {
if (myIcons[i] != null) {
myScaledIcons[i] = ((ScalableIcon)myIcons[i]).scale(getScale());
}
}
return myScaledIcons;
}
@Override
public LayeredIcon withJBUIPreScaled(boolean preScaled) {
super.withJBUIPreScaled(preScaled);
@@ -105,6 +146,7 @@ public class LayeredIcon extends JBUI.AuxScalableJBIcon {
((LayeredIcon)icon).checkIHaventIconInsideMe(this);
}
myIcons[layer] = icon;
myScaledIcons = null;
myHShifts[layer] = scaleVal(hShift, Scale.JBUI);
myVShifts[layer] = scaleVal(vShift, Scale.JBUI);
updateSize();
@@ -179,33 +221,16 @@ public class LayeredIcon extends JBUI.AuxScalableJBIcon {
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
if (updateJBUIScale()) updateSize();
for (int i = 0; i < myIcons.length; i++) {
Icon icon = getOrScale(i);
int i = 0;
for (Icon icon : myScaledIcons()) {
if (icon == null || myDisabledLayers[i]) continue;
int xOffset = x + scaleVal(myXShift + myHShifts[i], Scale.ARBITRARY);
int yOffset = y + scaleVal(myYShift + myVShifts[i], Scale.ARBITRARY);
icon.paintIcon(c, g, xOffset, yOffset);
i++;
}
}
private Icon getOrScale(int i) {
if (getScale() == 1f) {
return myIcons[i];
}
if (myScaledIcons == null) {
myScaledIcons = new Icon[myIcons.length];
}
Icon icon = myScaledIcons[i];
if (icon == null && myIcons[i] != null) {
icon = myIcons[i];
if (icon instanceof ScalableIcon) {
icon = myScaledIcons[i] = ((ScalableIcon)icon).scale(getScale());
}
}
return icon;
}
public boolean isLayerEnabled(int layer) {
return !myDisabledLayers[layer];
}
@@ -268,13 +293,4 @@ public class LayeredIcon extends JBUI.AuxScalableJBIcon {
public String toString() {
return "Layered icon. myIcons=" + Arrays.asList(myIcons);
}
@Override
public Icon scale(float scale) {
if (scale == getScale()) return this;
setScale(scale);
if (myScaledIcons != null) Arrays.fill(myScaledIcons, null);
return this;
}
}
@@ -17,6 +17,7 @@
package com.intellij.ui;
import com.intellij.openapi.util.ScalableIcon;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.JBUI;
import org.jetbrains.annotations.NotNull;
@@ -54,34 +55,40 @@ public class RowIcon extends JBUI.AuxScalableJBIcon {
updateSize();
}
@Override
public Icon scale(float scale) {
if (scale == getScale()) return this;
setScale(scale);
rescale();
return this;
protected RowIcon(RowIcon icon) {
super(icon);
myAlignment = icon.myAlignment;
myWidth = icon.myWidth;
myHeight = icon.myHeight;
myIcons = ArrayUtil.copyOf(icon.myIcons);
myScaledIcons = null;
}
private void rescale() {
if (getScale() == 1f) {
myScaledIcons = null;
return;
}
@Override
protected RowIcon copy() {
return new RowIcon(this);
}
private Icon[] myScaledIcons() {
if (myScaledIcons != null) {
return myScaledIcons;
}
if (getScale() == 1f) {
return myScaledIcons = myIcons;
}
for (Icon icon : myIcons) {
if (icon != null && !(icon instanceof ScalableIcon)) {
return;
setScale(1f);
return myScaledIcons = myIcons;
}
}
myScaledIcons = new Icon[myIcons.length];
for (int i = 0; i < myIcons.length; i++) {
ScalableIcon icon = (ScalableIcon)myIcons[i];
myScaledIcons[i] = icon == null ? null : icon.scale(getScale());
if (myIcons[i] != null) {
myScaledIcons[i] = ((ScalableIcon)myIcons[i]).scale(getScale());
}
}
updateSize();
return myScaledIcons;
}
@TestOnly
@@ -105,7 +112,7 @@ public class RowIcon extends JBUI.AuxScalableJBIcon {
public void setIcon(Icon icon, int layer) {
myIcons[layer] = icon;
rescale();
myScaledIcons = null;
updateSize();
}
@@ -113,17 +120,12 @@ public class RowIcon extends JBUI.AuxScalableJBIcon {
return myIcons[index];
}
public Icon[] getIcons() {
Icon[] icons = getScale() == 1f ? myIcons : myScaledIcons;
return icons == null ? myIcons : icons;
}
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
if (updateJBUIScale()) updateSize();
int _x = x;
int _y = y;
for (Icon icon : getIcons()) {
for (Icon icon : myScaledIcons()) {
if (icon == null) continue;
switch (myAlignment) {
case TOP: _y = y;
@@ -154,7 +156,7 @@ public class RowIcon extends JBUI.AuxScalableJBIcon {
private void updateSize() {
int width = 0;
int height = 0;
for (Icon icon : getIcons()) {
for (Icon icon : myIcons) {
if (icon == null) continue;
width += icon.getIconWidth();
//height += icon.getIconHeight();
@@ -22,6 +22,8 @@ import com.intellij.psi.util.PsiUtilCore;
import com.intellij.ui.SizedIcon;
import com.intellij.util.PlatformIcons;
import com.intellij.util.ui.EmptyIcon;
import com.intellij.util.ui.JBUI.ScalableJBIcon;
import com.intellij.util.ui.JBUI.ScalableJBIcon.Scale;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
@@ -45,9 +47,11 @@ public class DefaultLookupItemRenderer extends LookupElementRenderer<LookupItem>
@Nullable
public static Icon getRawIcon(final LookupElement item, boolean real) {
final Icon icon = _getRawIcon(item, real);
if (icon != null && icon.getIconHeight() > SAMPLE_ICON.getIconHeight()) {
return new SizedIcon(icon, icon.getIconWidth(), SAMPLE_ICON.getIconHeight());
Icon icon = _getRawIcon(item, real);
// align SAMPLE_ICON.getIconHeight() with the 'icon' scale
int sampleIconHeight = ScalableJBIcon.scaleVal(icon, SAMPLE_ICON.getIconHeight(), Scale.ARBITRARY);
if (icon != null && icon.getIconHeight() > sampleIconHeight) {
return new SizedIcon(icon, icon.getIconWidth(), sampleIconHeight, true);
}
return icon;
}
@@ -46,12 +46,13 @@ import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.Executor;
public class DeferredIconImpl<T> implements DeferredIcon, RetrievableIcon, ScalableIcon {
public class DeferredIconImpl<T> extends JBUI.CachingScalableJBIcon implements DeferredIcon, RetrievableIcon {
private static final Logger LOG = Logger.getInstance("#com.intellij.ui.DeferredIconImpl");
private static final int MIN_AUTO_UPDATE_MILLIS = 950;
private static final RepaintScheduler ourRepaintScheduler = new RepaintScheduler();
@NotNull
private volatile Icon myDelegateIcon;
private volatile Icon myScaledDelegateIcon;
private Function<T, Icon> myEvaluator;
private volatile boolean myIsScheduled;
private T myParam;
@@ -61,20 +62,39 @@ public class DeferredIconImpl<T> implements DeferredIcon, RetrievableIcon, Scala
private final boolean myAutoUpdatable;
private long myLastCalcTime;
private long myLastTimeSpent;
private float myScale = 1f;
private Icon myOriginalDeferredIcon = null;
private static final Executor ourIconsCalculatingExecutor = AppExecutorUtil.createBoundedApplicationPoolExecutor("ourIconsCalculating pool",1);
private final IconListener<T> myEvalListener;
private static final TransferToEDTQueue<Runnable> ourLaterInvocator = TransferToEDTQueue.createRunnableMerger("Deferred icon later invocator", 200);
protected DeferredIconImpl(DeferredIconImpl icon) {
super(icon);
myDelegateIcon = icon.myDelegateIcon;
myScaledDelegateIcon = icon.myDelegateIcon;
myEvaluator = icon.myEvaluator;
myIsScheduled = icon.myIsScheduled;
myParam = (T)icon.myParam;
myNeedReadAction = icon.myNeedReadAction;
myDone = icon.myDone;
myAutoUpdatable = icon.myAutoUpdatable;
myLastCalcTime = icon.myLastCalcTime;
myLastTimeSpent = icon.myLastTimeSpent;
myEvalListener = icon.myEvalListener;
}
@Override
public Icon scale(final float scaleFactor) {
if (scaleFactor != myScale && myDelegateIcon instanceof ScalableIcon) {
myDelegateIcon = ((ScalableIcon)myDelegateIcon).scale(myScale = scaleFactor);
protected DeferredIconImpl copy() {
return new DeferredIconImpl(this);
}
@Override
public void setScale(float scale) {
if (getScale() != scale && myDelegateIcon instanceof ScalableIcon) {
myScaledDelegateIcon = ((ScalableIcon)myDelegateIcon).scale(scale);
super.setScale(scale);
return;
}
return this;
}
private static class Holder {
@@ -92,6 +112,7 @@ public class DeferredIconImpl<T> implements DeferredIcon, RetrievableIcon, Scala
private DeferredIconImpl(Icon baseIcon, T param, boolean needReadAction, @NotNull Function<T, Icon> evaluator, @Nullable IconListener<T> listener, boolean autoUpdatable) {
myParam = param;
myDelegateIcon = nonNull(baseIcon);
myScaledDelegateIcon = myDelegateIcon;
myEvaluator = evaluator;
myNeedReadAction = needReadAction;
myEvalListener = listener;
@@ -102,9 +123,9 @@ public class DeferredIconImpl<T> implements DeferredIcon, RetrievableIcon, Scala
private void checkDelegationDepth() {
int depth = 0;
DeferredIconImpl each = this;
while (each.myDelegateIcon instanceof DeferredIconImpl && depth < 50) {
while (each.myScaledDelegateIcon instanceof DeferredIconImpl && depth < 50) {
depth++;
each = (DeferredIconImpl)each.myDelegateIcon;
each = (DeferredIconImpl)each.myScaledDelegateIcon;
}
if (depth >= 50) {
LOG.error("Too deep deferred icon nesting");
@@ -118,8 +139,8 @@ public class DeferredIconImpl<T> implements DeferredIcon, RetrievableIcon, Scala
@Override
public void paintIcon(final Component c, @NotNull final Graphics g, final int x, final int y) {
if (!(myDelegateIcon instanceof DeferredIconImpl && ((DeferredIconImpl)myDelegateIcon).myDelegateIcon instanceof DeferredIconImpl)) {
myDelegateIcon.paintIcon(c, g, x, y); //SOE protection
if (!(myScaledDelegateIcon instanceof DeferredIconImpl && ((DeferredIconImpl)myScaledDelegateIcon).myScaledDelegateIcon instanceof DeferredIconImpl)) {
myScaledDelegateIcon.paintIcon(c, g, x, y); //SOE protection
}
if (isDone() || myIsScheduled || PowerSaveMode.isEnabled()) {
@@ -131,7 +152,7 @@ public class DeferredIconImpl<T> implements DeferredIcon, RetrievableIcon, Scala
final Component paintingParent = SwingUtilities.getAncestorOfClass(PaintingParent.class, c);
final Rectangle paintingParentRec = paintingParent == null ? null : ((PaintingParent)paintingParent).getChildRec(c);
ourIconsCalculatingExecutor.execute(() -> {
int oldWidth = myDelegateIcon.getIconWidth();
int oldWidth = myScaledDelegateIcon.getIconWidth();
final Icon[] evaluated = new Icon[1];
final long startTime = System.currentTimeMillis();
@@ -156,11 +177,11 @@ public class DeferredIconImpl<T> implements DeferredIcon, RetrievableIcon, Scala
}
}
final Icon result = evaluated[0];
myDelegateIcon = result;
myScaledDelegateIcon = result;
checkDelegationDepth();
final boolean shouldRevalidate =
Registry.is("ide.tree.deferred.icon.invalidates.cache") && myDelegateIcon.getIconWidth() != oldWidth;
Registry.is("ide.tree.deferred.icon.invalidates.cache") && myScaledDelegateIcon.getIconWidth() != oldWidth;
ourLaterInvocator.offer(() -> {
setDone(result);
@@ -243,7 +264,7 @@ public class DeferredIconImpl<T> implements DeferredIcon, RetrievableIcon, Scala
@Nullable
@Override
public Icon retrieveIcon() {
return isDone() ? myDelegateIcon : evaluate();
return isDone() ? myScaledDelegateIcon : evaluate();
}
@NotNull
@@ -261,8 +282,8 @@ public class DeferredIconImpl<T> implements DeferredIcon, RetrievableIcon, Scala
checkDoesntReferenceThis(result);
}
if (myScale != 1f && result instanceof ScalableIcon) {
result = ((ScalableIcon)result).scale(myScale);
if (getScale() != 1f && result instanceof ScalableIcon) {
result = ((ScalableIcon)result).scale(getScale());
}
return result;
}
@@ -273,7 +294,7 @@ public class DeferredIconImpl<T> implements DeferredIcon, RetrievableIcon, Scala
}
if (icon instanceof DeferredIconImpl) {
checkDoesntReferenceThis(((DeferredIconImpl)icon).myDelegateIcon);
checkDoesntReferenceThis(((DeferredIconImpl)icon).myScaledDelegateIcon);
}
else if (icon instanceof LayeredIcon) {
for (Icon layer : ((LayeredIcon)icon).getAllLayers()) {
@@ -291,12 +312,12 @@ public class DeferredIconImpl<T> implements DeferredIcon, RetrievableIcon, Scala
@Override
public int getIconWidth() {
return myDelegateIcon.getIconWidth();
return myScaledDelegateIcon.getIconWidth();
}
@Override
public int getIconHeight() {
return myDelegateIcon.getIconHeight();
return myScaledDelegateIcon.getIconHeight();
}
public boolean isDone() {
@@ -367,11 +388,11 @@ public class DeferredIconImpl<T> implements DeferredIcon, RetrievableIcon, Scala
private boolean isDeferredAndEqual(Icon icon) {
return icon instanceof DeferredIconImpl &&
Comparing.equal(myParam, ((DeferredIconImpl)icon).myParam) &&
equalIcons(myDelegateIcon, ((DeferredIconImpl)icon).myDelegateIcon);
equalIcons(myScaledDelegateIcon, ((DeferredIconImpl)icon).myScaledDelegateIcon);
}
@Override
public String toString() {
return "Deferred. Base=" + myDelegateIcon;
return "Deferred. Base=" + myScaledDelegateIcon;
}
}
@@ -24,27 +24,69 @@ import java.awt.*;
/**
* @author peter
*/
public class SizedIcon extends JBUI.ScalableJBIcon {
public class SizedIcon extends JBUI.CachingScalableJBIcon {
private final int myWidth;
private final int myHeight;
private final Icon myDelegate;
private Icon myScaledDelegate;
public SizedIcon(Icon delegate, int width, int height) {
this(delegate, width, height, false);
}
/**
* @param delegate the delegate icon
* @param width this icon width
* @param height this icon height
* @param inDelegateScale whether to align this icon scale with the delegate icon scale
*/
public SizedIcon(Icon delegate, int width, int height, boolean inDelegateScale) {
float scale = 1f;
if (inDelegateScale && delegate instanceof ScalableIcon) {
setScale(scale = ((ScalableIcon)delegate).getScale());
}
myScaledDelegate = myDelegate = delegate;
myWidth = width;
myHeight = height;
myWidth = (int)(width / scale);
myHeight = (int)(height / scale);
}
protected SizedIcon(SizedIcon icon) {
super(icon);
myWidth = icon.myWidth;
myHeight = icon.myHeight;
myDelegate = icon.myDelegate;
myScaledDelegate = null;
}
@Override
protected SizedIcon copy() {
return new SizedIcon(this);
}
private Icon myScaledIcon() {
if (myScaledDelegate != null) {
return myScaledDelegate;
}
if (getScale() == 1f) {
return myScaledDelegate = myDelegate;
}
if (!(myDelegate instanceof ScalableIcon)) {
setScale(1f);
return myScaledDelegate = myDelegate;
}
return myScaledDelegate = ((ScalableIcon)myDelegate).scale(getScale());
}
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
int dx = scaleVal(myWidth) - myScaledDelegate.getIconWidth();
int dy = scaleVal(myHeight) - myScaledDelegate.getIconHeight();
Icon icon = myScaledIcon();
int dx = scaleVal(myWidth) - icon.getIconWidth();
int dy = scaleVal(myHeight) - icon.getIconHeight();
if (dx > 0 || dy > 0) {
myScaledDelegate.paintIcon(c, g, x + dx/2, y + dy/2);
icon.paintIcon(c, g, x + dx / 2, y + dy / 2);
}
else {
myScaledDelegate.paintIcon(c, g, x, y);
icon.paintIcon(c, g, x, y);
}
}
@@ -55,15 +97,4 @@ public class SizedIcon extends JBUI.ScalableJBIcon {
public int getIconHeight() {
return scaleVal(myHeight);
}
@Override
public Icon scale(float scale) {
if (scale == getScale()) return this;
if (myDelegate instanceof ScalableIcon) {
setScale(scale);
myScaledDelegate = ((ScalableIcon)myDelegate).scale(scale);
}
return this;
}
}
@@ -455,6 +455,11 @@ public final class IconLoader {
return myUrl.toString();
}
@Override
public float getScale() {
return 1f;
}
@Override
public Icon scale(float scale) {
if (scale == 1f) return this;
@@ -519,7 +524,7 @@ public final class IconLoader {
}
}
public abstract static class LazyIcon extends JBUI.AuxScalableJBIcon {
public abstract static class LazyIcon extends JBUI.AuxJBIcon {
private boolean myWasComputed;
private Icon myIcon;
private boolean isDarkVariant = USE_DARK_ICONS;
@@ -577,18 +582,6 @@ public final class IconLoader {
}
return icon;
}
@Override
public Icon scale(float scale) {
if (scale == getScale()) return this;
getOrComputeIcon();
if (myIcon instanceof ScalableIcon) {
setScale(scale);
myIcon = ((ScalableIcon)myIcon).scale(scale);
}
return this;
}
}
private static class LabelHolder {
@@ -23,13 +23,22 @@ 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.
* @return the scale of the icon relative to its origin
*/
float getScale();
/**
* Creates and returns a scaled instance of the icon.
* The icon is scaled relative to its origin, that is {@code scale(1f)} will
* return the icon in its original scale.
*
* To scale this instance by {@code n} times: {@code scale(n * getScale())}.
*
* Note that the methods {@link #getIconWidth()} and {@link #getIconHeight()}
* should return the scaled size of the icon.
*
* @param scaleFactor scale
* @return scaled icon with width getIconWidth() * scaleFactor and height getIconHeight() * scaleFactor
* @return scaled icon instance
*/
Icon scale(float scaleFactor);
}
@@ -907,6 +907,24 @@ public class ArrayUtil extends ArrayUtilRt {
}
}
@Nullable
public static <T> T[] copyOf(@Nullable T[] original) {
if (original == null) return null;
return (T[]) Arrays.copyOf(original, original.length, original.getClass());
}
@Nullable
public static boolean[] copyOf(@Nullable boolean[] original) {
if (original == null) return null;
return Arrays.copyOf(original, original.length);
}
@Nullable
public static int[] copyOf(@Nullable int[] original) {
if (original == null) return null;
return Arrays.copyOf(original, original.length);
}
@NotNull
public static <T> T[] stripTrailingNulls(@NotNull T[] array) {
return array.length != 0 && array[array.length-1] == null ? Arrays.copyOf(array, trailingNullsIndex(array)) : array;
@@ -366,6 +366,7 @@ public class JBUI {
EFFECTIVE // effective scale
}
@Override
public float getScale() {
return myScale;
}
@@ -399,6 +400,23 @@ public class JBUI {
return super.scaleVal(value * myScale);
}
}
/**
* Scales the value in the icon's scale.
*/
public static int scaleVal(Icon icon, int value, Scale type) {
return (int)scaleVal(icon, (float)value, type);
}
/**
* Scales the value in the icon's scale.
*/
public static float scaleVal(Icon icon, float value, Scale type) {
if (icon instanceof ScalableJBIcon) {
return ((ScalableJBIcon)icon).scaleVal(value, type);
}
return value;
}
}
/**
@@ -422,9 +440,8 @@ public class JBUI {
*/
@Override
public Icon scale(float scale) {
if (scale == 1f) return this;
if (scale == getScale()) return this;
scale = scaleVal(scale, Scale.ARBITRARY); // accumulate scale
if (myScaledIconCache == null || myScaledIconCache.getScale() != scale) {
myScaledIconCache = copy();
myScaledIconCache.setScale(scale);
@@ -433,7 +450,7 @@ public class JBUI {
}
/**
* @return a deep copy of this icon instance
* @return a copy of this icon instance
*/
@NotNull
protected abstract T copy();
@@ -481,9 +498,15 @@ public class JBUI {
*
* @author tav
*/
public static abstract class AuxScalableJBIcon extends ScalableJBIcon implements AuxJBUIScale {
public static abstract class AuxScalableJBIcon extends CachingScalableJBIcon implements AuxJBUIScale {
private float myCachedJBUIScale = JBUI.scale(1f);
protected AuxScalableJBIcon() {}
protected AuxScalableJBIcon(AuxScalableJBIcon icon) {
super(icon);
}
@Override
public boolean updateJBUIScale() {
if (needUpdateJBUIScale()) {