mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Added ScaleContext.overrideScale (and IconUtil.overrideScale)
- added the case to IconScaleTest - added CopyableIcon.deepCopy (and IconUtil.deepCopy), implemented in LayeredIcon, RowIcon
This commit is contained in:
@@ -19,6 +19,7 @@ import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.util.IconLoader;
|
||||
import com.intellij.openapi.util.IconLoader.DarkIconProvider;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.IconUtil;
|
||||
import com.intellij.util.ui.JBCachingScalableIcon;
|
||||
import org.intellij.lang.annotations.MagicConstant;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -82,6 +83,16 @@ public class LayeredIcon extends JBCachingScalableIcon<LayeredIcon> implements D
|
||||
return new LayeredIcon(this);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public LayeredIcon deepCopy() {
|
||||
LayeredIcon icon = new LayeredIcon(this);
|
||||
for (int i = 0; i < icon.myIcons.length; i++) {
|
||||
icon.myIcons[i] = IconUtil.copy(icon.myIcons[i], null);
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Icon[] myScaledIcons() {
|
||||
if (myScaledIcons != null) {
|
||||
|
||||
@@ -65,6 +65,16 @@ public class RowIcon extends JBCachingScalableIcon<RowIcon> implements DarkIconP
|
||||
return new RowIcon(this);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public RowIcon deepCopy() {
|
||||
RowIcon icon = new RowIcon(this);
|
||||
for (int i = 0; i < icon.myIcons.length; i++) {
|
||||
icon.myIcons[i] = IconUtil.copy(icon.myIcons[i], null);
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Icon[] myScaledIcons() {
|
||||
if (myScaledIcons != null) {
|
||||
|
||||
@@ -458,7 +458,17 @@ public class IconUtil {
|
||||
*/
|
||||
@Contract("null, _->null; !null, _->!null")
|
||||
public static Icon copy(@Nullable Icon icon, @Nullable Component ancestor) {
|
||||
return IconLoader.copy(icon, ancestor);
|
||||
return IconLoader.copy(icon, ancestor, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a deep copy of the provided {@code icon}.
|
||||
*
|
||||
* @see CopyableIcon
|
||||
*/
|
||||
@Contract("null, _->null; !null, _->!null")
|
||||
public static Icon deepCopy(@Nullable Icon icon, @Nullable Component ancestor) {
|
||||
return IconLoader.copy(icon, ancestor, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -521,6 +531,26 @@ public class IconUtil {
|
||||
return scale(icon, ancestor, scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the provided scale in the icon's scale context and in the composited icon's scale contexts (when applicable).
|
||||
*
|
||||
* @see JBUIScale.UserScaleContext#overrideScale(JBUIScale.Scale)
|
||||
*/
|
||||
@NotNull
|
||||
public static Icon overrideScale(@NotNull Icon icon, JBUIScale.Scale scale) {
|
||||
if (icon instanceof CompositeIcon) {
|
||||
CompositeIcon compositeIcon = (CompositeIcon)icon;
|
||||
for (int i = 0; i < compositeIcon.getIconCount(); i++) {
|
||||
Icon subIcon = compositeIcon.getIcon(i);
|
||||
if (subIcon != null) overrideScale(subIcon, scale);
|
||||
}
|
||||
}
|
||||
if (icon instanceof ScaleContextAware) {
|
||||
((ScaleContextAware)icon).getScaleContext().overrideScale(scale);
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Icon colorize(@NotNull Icon source, @NotNull Color color) {
|
||||
return colorize(source, color, false);
|
||||
|
||||
@@ -6,14 +6,9 @@ import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.SystemInfo;
|
||||
import com.intellij.testFramework.PlatformTestUtil;
|
||||
import com.intellij.testFramework.fixtures.BareTestFixtureTestCase;
|
||||
import com.intellij.ui.DeferredIconImpl;
|
||||
import com.intellij.ui.LayeredIcon;
|
||||
import com.intellij.ui.RestoreScaleRule;
|
||||
import com.intellij.ui.RowIcon;
|
||||
import com.intellij.ui.*;
|
||||
import com.intellij.util.IconUtil;
|
||||
import com.intellij.util.ui.JBUIScale.UserScaleContext;
|
||||
import com.intellij.util.ui.JBUIScale.ScaleContext;
|
||||
import com.intellij.util.ui.JBUIScale.ScaleContextAware;
|
||||
import com.intellij.util.ui.JBUIScale.*;
|
||||
import com.intellij.util.ui.paint.ImageComparator;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.ClassRule;
|
||||
@@ -28,8 +23,7 @@ import java.net.MalformedURLException;
|
||||
|
||||
import static com.intellij.util.ui.JBUIScale.DerivedScaleType.DEV_SCALE;
|
||||
import static com.intellij.util.ui.JBUIScale.DerivedScaleType.EFF_USR_SCALE;
|
||||
import static com.intellij.util.ui.JBUIScale.ScaleType.SYS_SCALE;
|
||||
import static com.intellij.util.ui.JBUIScale.ScaleType.USR_SCALE;
|
||||
import static com.intellij.util.ui.JBUIScale.ScaleType.*;
|
||||
import static com.intellij.util.ui.TestScaleHelper.*;
|
||||
|
||||
/**
|
||||
@@ -40,6 +34,7 @@ import static com.intellij.util.ui.TestScaleHelper.*;
|
||||
public class IconScaleTest extends BareTestFixtureTestCase {
|
||||
private static final int ICON_BASE_SIZE = 16;
|
||||
private static final float ICON_OBJ_SCALE = 1.75f;
|
||||
private static final float ICON_OVER_USR_SCALE = 1.0f;
|
||||
|
||||
@ClassRule
|
||||
public static final ExternalResource manageState = new RestoreScaleRule();
|
||||
@@ -97,33 +92,51 @@ public class IconScaleTest extends BareTestFixtureTestCase {
|
||||
test(new RowIcon(new CachedImageIcon(new File(getIconPath()).toURI().toURL())), UserScaleContext.create(ctx));
|
||||
}
|
||||
|
||||
private static void test(Icon icon, UserScaleContext bctx) {
|
||||
((ScaleContextAware)icon).updateScaleContext(bctx);
|
||||
private static void test(Icon icon, UserScaleContext iconCtx) {
|
||||
((ScaleContextAware)icon).updateScaleContext(iconCtx);
|
||||
|
||||
ScaleContext ctx = ScaleContext.create(bctx);
|
||||
ScaleContext ctx = ScaleContext.create(iconCtx);
|
||||
|
||||
/*
|
||||
* (A) normal conditions
|
||||
*/
|
||||
|
||||
//noinspection UnnecessaryLocalVariable
|
||||
Icon iconA = icon;
|
||||
double usrSize2D = ctx.apply(ICON_BASE_SIZE, EFF_USR_SCALE);
|
||||
int usrSize = (int)Math.round(usrSize2D);
|
||||
int devSize = (int)Math.round(ctx.apply(usrSize2D, DEV_SCALE));
|
||||
|
||||
assertEquals("unexpected icon user width " + bctx, usrSize, icon.getIconWidth());
|
||||
assertEquals("unexpected icon user height " + bctx, usrSize, icon.getIconHeight());
|
||||
assertEquals("unexpected icon real width " + bctx, devSize, ImageUtil.getRealWidth(IconUtil.toImage(icon, ctx)));
|
||||
assertEquals("unexpected icon real height " + bctx, devSize, ImageUtil.getRealHeight(IconUtil.toImage(icon, ctx)));
|
||||
assertIcon(iconA, iconCtx, usrSize, devSize);
|
||||
|
||||
Icon scaledIcon = IconUtil.scale(icon, null, ICON_OBJ_SCALE);
|
||||
/*
|
||||
* (B) override scale
|
||||
*/
|
||||
if (!(icon instanceof RetrievableIcon)) { // RetrievableIcon may return a copy of its wrapped icon and we may fail to override scale in the origin.
|
||||
|
||||
assertNotSame("scaled instance of the icon " + bctx, icon, scaledIcon);
|
||||
assertEquals("ScaleContext of the original icon changed " + bctx, bctx, ((ScaleContextAware)icon).getScaleContext());
|
||||
Icon iconB = IconUtil.overrideScale(IconUtil.deepCopy(icon, null), USR_SCALE.of(ICON_OVER_USR_SCALE));
|
||||
|
||||
usrSize2D = ICON_BASE_SIZE * ICON_OVER_USR_SCALE * ctx.getScale(OBJ_SCALE);
|
||||
usrSize = (int)Math.round(usrSize2D);
|
||||
devSize = (int)Math.round(ctx.apply(usrSize2D, DEV_SCALE));
|
||||
|
||||
assertIcon(iconB, iconCtx, usrSize, devSize);
|
||||
}
|
||||
|
||||
/*
|
||||
* (C) scale icon
|
||||
*/
|
||||
Icon iconC = IconUtil.scale(icon, null, ICON_OBJ_SCALE);
|
||||
|
||||
assertNotSame("scaled instance of the icon " + iconCtx, icon, iconC);
|
||||
assertEquals("ScaleContext of the original icon changed " + iconCtx, iconCtx, ((ScaleContextAware)icon).getScaleContext());
|
||||
|
||||
usrSize2D = ctx.apply(ICON_BASE_SIZE, EFF_USR_SCALE);
|
||||
double scaledUsrSize2D = usrSize2D * ICON_OBJ_SCALE;
|
||||
int scaledUsrSize = (int)Math.round(scaledUsrSize2D);
|
||||
int scaledDevSize = (int)Math.round(ctx.apply(scaledUsrSize2D, DEV_SCALE));
|
||||
|
||||
assertEquals("unexpected scaled icon user width " + bctx, scaledUsrSize, scaledIcon.getIconWidth());
|
||||
assertEquals("unexpected scaled icon user height " + bctx, scaledUsrSize, scaledIcon.getIconHeight());
|
||||
assertEquals("unexpected scaled icon real width " + bctx, scaledDevSize, ImageUtil.getRealWidth(IconUtil.toImage(scaledIcon, ctx)));
|
||||
assertEquals("unexpected scaled icon real height " + bctx, scaledDevSize, ImageUtil.getRealHeight(IconUtil.toImage(scaledIcon, ctx)));
|
||||
assertIcon(iconC, iconCtx, scaledUsrSize, scaledDevSize);
|
||||
|
||||
// Additionally check that the original image hasn't changed after scaling
|
||||
Pair<BufferedImage, Graphics2D> pair = createImageAndGraphics(ctx.getScale(DEV_SCALE), icon.getIconWidth(), icon.getIconHeight());
|
||||
@@ -138,6 +151,14 @@ public class IconScaleTest extends BareTestFixtureTestCase {
|
||||
new ImageComparator.AASmootherComparator(0.1, 0.1, new Color(0, 0, 0, 0)), goldImage, iconImage, null);
|
||||
}
|
||||
|
||||
static void assertIcon(Icon icon, UserScaleContext iconCtx, int usrSize, int devSize) {
|
||||
ScaleContext ctx = ScaleContext.create(iconCtx);
|
||||
assertEquals("unexpected icon user width (ctx: " + iconCtx + ")", usrSize, icon.getIconWidth());
|
||||
assertEquals("unexpected icon user height (ctx: " + iconCtx + ")", usrSize, icon.getIconHeight());
|
||||
assertEquals("unexpected icon real width (ctx: " + iconCtx + ")", devSize, ImageUtil.getRealWidth(IconUtil.toImage(icon, ctx)));
|
||||
assertEquals("unexpected icon real height (ctx: " + iconCtx + ")", devSize, ImageUtil.getRealHeight(IconUtil.toImage(icon, ctx)));
|
||||
}
|
||||
|
||||
private static String getIconPath() {
|
||||
return PlatformTestUtil.getPlatformTestDataPath() + "ui/abstractClass.svg";
|
||||
}
|
||||
|
||||
@@ -17,4 +17,12 @@ public interface CopyableIcon extends Icon {
|
||||
*/
|
||||
@NotNull
|
||||
Icon copy();
|
||||
|
||||
/**
|
||||
* Returns a copy of this icon (see {@link #copy()}) trying to deep-copy composited icons when applicable.
|
||||
*/
|
||||
@NotNull
|
||||
default Icon deepCopy() {
|
||||
return copy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,11 +324,11 @@ public final class IconLoader {
|
||||
}
|
||||
}
|
||||
|
||||
@Contract("null, _->null; !null, _->!null")
|
||||
public static Icon copy(@Nullable Icon icon, @Nullable Component ancestor) {
|
||||
@Contract("null, _, _->null; !null, _, _->!null")
|
||||
public static Icon copy(@Nullable Icon icon, @Nullable Component ancestor, boolean deepCopy) {
|
||||
if (icon == null) return null;
|
||||
if (icon instanceof CopyableIcon) {
|
||||
return ((CopyableIcon)icon).copy();
|
||||
return deepCopy ? ((CopyableIcon)icon).deepCopy() : ((CopyableIcon)icon).copy();
|
||||
}
|
||||
BufferedImage image = UIUtil.createImage(ancestor, icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g = image.createGraphics();
|
||||
@@ -941,7 +941,7 @@ public final class IconLoader {
|
||||
@NotNull
|
||||
@Override
|
||||
public Icon copy() {
|
||||
return IconLoader.copy(getOrComputeIcon(), null);
|
||||
return IconLoader.copy(getOrComputeIcon(), null, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.awt.image.ImageObserver;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
@@ -236,6 +237,7 @@ public class JBUIScale {
|
||||
}
|
||||
}
|
||||
|
||||
// [tav] todo: [User]ScaleContext is thread-unsafe, should it be thread-safe?
|
||||
/**
|
||||
* Represents a snapshot of the user space scale factors: {@link ScaleType#USR_SCALE} and {@link ScaleType#OBJ_SCALE}).
|
||||
* The context can be associated with a UI object (see {@link ScaleContextAware}) to define its HiDPI behaviour.
|
||||
@@ -250,6 +252,7 @@ public class JBUIScale {
|
||||
protected double pixScale = usrScale.value;
|
||||
|
||||
private List<UpdateListener> listeners;
|
||||
private EnumSet<ScaleType> overriddenScales;
|
||||
|
||||
protected UserScaleContext() {
|
||||
}
|
||||
@@ -294,14 +297,49 @@ public class JBUIScale {
|
||||
return usrScale.value * objScale.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permanently overrides the provided scale (the scale won't be changed on subsequent {@link #update()}).
|
||||
* Can be used to make a UI object user scale independent:
|
||||
* <p>
|
||||
* <code>
|
||||
* ((ScaleContextAware)uiObject).getScaleContext().overrideScale(USR_SCALE.of(1.0));
|
||||
* </code>
|
||||
*
|
||||
* @param scale the new scale to override
|
||||
* @return whether the new scale updated the current value
|
||||
* @see ScaleType#of(double)
|
||||
*/
|
||||
public boolean overrideScale(@NotNull Scale scale) {
|
||||
if (overriddenScales != null) {
|
||||
overriddenScales.remove(scale.type); // previous override should not prevent this override
|
||||
}
|
||||
boolean updated = setScale(scale);
|
||||
|
||||
if (overriddenScales == null) {
|
||||
overriddenScales = EnumSet.of(scale.type);
|
||||
}
|
||||
else {
|
||||
overriddenScales.add(scale.type);
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
protected boolean isScaleOverridden(@NotNull Scale scale) {
|
||||
return overriddenScales != null && overriddenScales.contains(scale.type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the new scale (system scale is ignored). Use {@link ScaleType#of(double)} to provide the new scale.
|
||||
* Note, the new scale value can be change on subsequent {@link #update()}. Use {@link #overrideScale(Scale)}
|
||||
* to set a scale permanently.
|
||||
*
|
||||
* @param scale the new scale to set
|
||||
* @return whether the new scale updated the current value
|
||||
* @see ScaleType#of(double)
|
||||
*/
|
||||
public boolean setScale(@NotNull Scale scale) {
|
||||
if (isScaleOverridden(scale)) return false;
|
||||
|
||||
boolean updated = false;
|
||||
switch (scale.type) {
|
||||
case USR_SCALE:
|
||||
@@ -618,6 +656,8 @@ public class JBUIScale {
|
||||
*/
|
||||
@Override
|
||||
public boolean setScale(@NotNull Scale scale) {
|
||||
if (isScaleOverridden(scale)) return false;
|
||||
|
||||
if (scale.type == SYS_SCALE) {
|
||||
boolean updated = !sysScale.equals(scale);
|
||||
sysScale = scale;
|
||||
|
||||
Reference in New Issue
Block a user