IDEA-242075 "Coordinate out of bounds" when rendering a disabled button with JBHiDPIScaledImage icon

GitOrigin-RevId: 15c786dc1a70304c376ef05a8fb5273a15e0c542
This commit is contained in:
Anton Tarasov
2020-06-03 19:00:26 +03:00
committed by intellij-monorepo-bot
parent 79e8bf394f
commit 233ac79ccc
4 changed files with 79 additions and 23 deletions
@@ -14,10 +14,9 @@ import com.intellij.openapi.util.*;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.TableActions;
import com.intellij.ui.scale.JBUIScale;
import com.intellij.ui.scale.ScaleContext;
import com.intellij.util.Alarm;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.StartupUiUtil;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.*;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -38,6 +37,7 @@ import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.List;
/**
* @author Konstantin Bulenkov
@@ -463,12 +463,15 @@ public class DarculaLaf extends BasicLookAndFeel implements UserDataHolder {
public Icon getDisabledIcon(JComponent component, Icon icon) {
if (icon == null) return null;
ScaleContext ctx = ScaleContext.create(component);
icon = MultiResolutionImageProvider.convertFromJBIcon(icon, ctx);
Icon disabledIcon = super.getDisabledIcon(component, icon);
disabledIcon = MultiResolutionImageProvider.convertFromMRIcon(disabledIcon, ctx);
if (disabledIcon != null) {
return disabledIcon;
}
return IconLoader.getDisabledIcon(icon);
return IconLoader.getDisabledIcon(icon, component);
}
@Override
@@ -183,15 +183,7 @@ public abstract class AppIcon {
if (appImage == null) return null;
// [tav] expecting two resolution variants for the dock icon: 128x128, 256x256
if (MultiResolutionImageProvider.isMultiResolutionImage(appImage)) {
List<Image> variants = MultiResolutionImageProvider.getAccessor(appImage).getResolutionVariants();
int width = appImage.getWidth(null);
for (Image img : variants) {
if (img.getWidth(null) > width) {
appImage = img;
}
}
}
appImage = MultiResolutionImageProvider.getMaxSizeResolutionVariant(appImage);
myAppImage = ImageUtil.toBufferedImage(appImage);
}
catch (NoSuchMethodException e) {
@@ -433,6 +433,14 @@ public final class IconLoader {
*/
@NotNull
public static Icon getDisabledIcon(@NotNull Icon icon) {
return getDisabledIcon(icon, null);
}
/**
* Same as {@link #getDisabledIcon(Icon)} with an ancestor component for HiDPI-awareness.
*/
@NotNull
public static Icon getDisabledIcon(@NotNull Icon icon, @Nullable Component ancestor) {
if (!ourIsActivated) {
return icon;
}
@@ -442,7 +450,7 @@ public final class IconLoader {
Icon disabledIcon = ourIcon2DisabledIcon.get(icon);
if (disabledIcon == null) {
disabledIcon = ConcurrencyUtil.cacheOrGet(ourIcon2DisabledIcon, icon,
filterIcon(icon, UIUtil::getGrayFilter/* returns laf-aware instance */, null)); // [tav] todo: lack ancestor
filterIcon(icon, UIUtil::getGrayFilter/* returns laf-aware instance */, ancestor));
}
return disabledIcon;
}
@@ -1,40 +1,42 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.util.ui;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.SystemInfoRt;
import com.intellij.ui.scale.ScaleContext;
import com.intellij.util.JBHiDPIScaledImage;
import com.intellij.util.MethodInvocator;
import org.imgscalr.Scalr;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImageOp;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
/**
* Provides access (or converter) to {@code java.awt.image.MultiResolutionImage} available since JDK 9.
*
* @author tav
*/
@ApiStatus.Experimental
public final class MultiResolutionImageProvider {
/**
* An accessor to the {@code MultiResolutionImage}'s resolution variants methods.
*/
public static class Accessor {
private static final Class MRI_CLASS;
private static final Class<?> MRI_CLASS;
private static final MethodInvocator GET_RESOLUTION_VARIANTS_METHOD;
private static final MethodInvocator GET_RESOLUTION_VARIANT_METHOD;
private final Image myMRImage;
static {
Class cls = null;
Class<?> cls = null;
MethodInvocator m1 = null;
MethodInvocator m2 = null;
if (SystemInfoRt.IS_AT_LEAST_JAVA9) {
@@ -71,6 +73,7 @@ public final class MultiResolutionImageProvider {
/**
* @see {@code java.awt.image.MultiResolutionImage.getResolutionVariant}
*/
@SuppressWarnings("unused")
public Image getResolutionVariant(double width, double height) {
if (!isMultiResolutionImage(myMRImage)) {
if (!checkSize(myMRImage)) {
@@ -87,11 +90,11 @@ public final class MultiResolutionImageProvider {
* A converter from {@link JBHiDPIScaledImage} to {@code MultiResolutionImage}.
*/
private static class Converter {
private static final Constructor BMRI_CLASS_CTOR;
private static final Constructor<?> BMRI_CLASS_CTOR;
static {
Class cls = null;
Constructor ctor = null;
Class<?> cls = null;
Constructor<?> ctor = null;
if (SystemInfoRt.IS_AT_LEAST_JAVA9) {
try {
cls = Class.forName("java.awt.image.BaseMultiResolutionImage");
@@ -153,7 +156,7 @@ public final class MultiResolutionImageProvider {
* If the provided image is not {@code JBHiDPIScaledImage} the returned {@code MultiResolutionImage} will
* default to the provided image's single resolution variant.
*/
@Nullable
@Contract("null -> null; !null -> !null")
public static Image convertFromJBImage(@Nullable Image jbImage) {
if (jbImage == null) return null;
@@ -164,6 +167,56 @@ public final class MultiResolutionImageProvider {
return Converter.convert(jbImage);
}
/**
* Converts the provided icon with {@link JBHiDPIScaledImage} to an {@link ImageIcon} with {@code MultiResolutionImage}.
* If the provided icon's image is not {@code JBHiDPIScaledImage} the returned icon's {@code MultiResolutionImage} will
* default to the provided image's single resolution variant.
*/
@Contract("null, _ -> null; !null, _ -> !null")
public static Icon convertFromJBIcon(@Nullable Icon jbIcon, @Nullable ScaleContext ctx) {
if (jbIcon == null) return null;
Image image = Objects.requireNonNull(IconLoader.toImage(jbIcon, ctx));
image = convertFromJBImage(image);
return new ImageIcon(image);
}
/**
* Returns the max-size resolution variant image of the provided {@code MultiResolutionImage}.
* If the provided image is not {@code MultiResolutionImage} then returns same image.
*/
@Contract("null -> null; !null -> !null")
public static Image getMaxSizeResolutionVariant(@Nullable Image mrImage) {
if (isMultiResolutionImage(mrImage)) {
List<Image> variants = getAccessor(mrImage).getResolutionVariants();
int width = mrImage.getWidth(null);
for (Image img : variants) {
if (img.getWidth(null) >= width) {
mrImage = img;
}
}
}
return mrImage;
}
/**
* Converts from the provided icon with {@code MultiResolutionImage} to an icon with {@link JBHiDPIScaledImage}.
* If the provided icon's image is not {@code MultiResolutionImage} then returns same icon.
*/
@Contract("null, _ -> null; !null, _ -> !null")
public static Icon convertFromMRIcon(@Nullable Icon mrIcon, @Nullable ScaleContext ctx) {
if (mrIcon == null) return null;
if (ctx == null) ctx = ScaleContext.create();
Image image = Objects.requireNonNull(IconLoader.toImage(mrIcon, ctx));
if (isMultiResolutionImage(image)) {
return mrIcon;
}
image = getMaxSizeResolutionVariant(image);
image = ImageUtil.ensureHiDPI(image, ctx);
return new JBImageIcon(image);
}
/**
* Returns an accessor to the provided {@code MultiResolutionImage}.
* If the provided image is not {@code MultiResolutionImage} the resolution variants methods will default to the provided image.