IDEA-171404 allow UI scale exceed 2x

- UI scale can exceed 2x now
- JBUI.ScaleType.OBJ_SCALE is introduced
- JBUI.Scaler & JBUI.ScaleContext is introduced
- JBUI.JBIcon and subclasses are refactored
- CachedImageIcon is simplified
- ShadowPainter scales shadow now
- Transitioning from int/float to double where applicable
- double [x, y, width, height] is rounded this way [floor, floor, ceil, ceil]
- JBDimension is backed by double size
- JBDimension rescales itself via new methods: size()/width()/width2D()
This commit is contained in:
Anton Tarasov
2017-09-20 16:24:16 +03:00
parent 07ce923032
commit 53317a0cb6
24 changed files with 1058 additions and 664 deletions
@@ -18,7 +18,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 com.intellij.util.ui.JBUI.CachingScalableJBIcon;
import org.intellij.lang.annotations.MagicConstant;
import org.jetbrains.annotations.NotNull;
@@ -26,7 +26,12 @@ import javax.swing.*;
import java.awt.*;
import java.util.Arrays;
public class LayeredIcon extends JBUI.UpdatingScalableJBIcon<LayeredIcon> {
import static com.intellij.util.ui.JBUI.ScaleType.OBJ_SCALE;
import static com.intellij.util.ui.JBUI.ScaleType.USR_SCALE;
import static java.lang.Math.ceil;
import static java.lang.Math.floor;
public class LayeredIcon extends CachingScalableJBIcon<LayeredIcon> {
private static final Logger LOG = Logger.getInstance("#com.intellij.ui.LayeredIcon");
private final Icon[] myIcons;
private Icon[] myScaledIcons;
@@ -96,8 +101,8 @@ public class LayeredIcon extends JBUI.UpdatingScalableJBIcon<LayeredIcon> {
}
@Override
public LayeredIcon withJBUIPreScaled(boolean preScaled) {
super.withJBUIPreScaled(preScaled);
public LayeredIcon withIconPreScaled(boolean preScaled) {
super.withIconPreScaled(preScaled);
updateSize();
return this;
}
@@ -218,13 +223,13 @@ public class LayeredIcon extends JBUI.UpdatingScalableJBIcon<LayeredIcon> {
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
if (updateJBUIScale()) updateSize();
if (getScaleContext().update()) updateSize();
Icon[] icons = myScaledIcons();
for (int i = 0; i < icons.length; i++) {
Icon icon = icons[i];
if (icon == null || myDisabledLayers[i]) continue;
int xOffset = x + scaleVal(myXShift + myHShifts(i), Scale.INSTANCE);
int yOffset = y + scaleVal(myYShift + myVShifts(i), Scale.INSTANCE);
int xOffset = (int)floor(x + scaleVal(myXShift + myHShifts(i), OBJ_SCALE));
int yOffset = (int)floor(y + scaleVal(myYShift + myVShifts(i), OBJ_SCALE));
icon.paintIcon(c, g, xOffset, yOffset);
}
}
@@ -239,26 +244,26 @@ public class LayeredIcon extends JBUI.UpdatingScalableJBIcon<LayeredIcon> {
@Override
public int getIconWidth() {
if (myWidth <= 1 || updateJBUIScale()) {
if (getScaleContext().update() || myWidth <= 1) {
updateSize();
}
return scaleVal(myWidth, Scale.INSTANCE);
return (int)ceil(scaleVal(myWidth, OBJ_SCALE));
}
@Override
public int getIconHeight() {
if (myHeight <= 1 || updateJBUIScale()) {
if (getScaleContext().update() || myHeight <= 1) {
updateSize();
}
return scaleVal(myHeight, Scale.INSTANCE);
return (int)ceil(scaleVal(myHeight, OBJ_SCALE));
}
private int myHShifts(int i) {
return scaleVal(myHShifts[i], Scale.JBUI);
return (int)floor(scaleVal(myHShifts[i], USR_SCALE));
}
private int myVShifts(int i) {
return scaleVal(myVShifts[i], Scale.JBUI);
return (int)floor(scaleVal(myVShifts[i], USR_SCALE));
}
protected void updateSize() {
@@ -20,6 +20,7 @@ import com.intellij.openapi.util.ScalableIcon;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.JBUI.CachingScalableJBIcon;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;
@@ -28,7 +29,10 @@ import java.awt.*;
import java.util.Arrays;
import java.util.List;
public class RowIcon extends JBUI.UpdatingScalableJBIcon<RowIcon> {
import static com.intellij.util.ui.JBUI.ScaleType.OBJ_SCALE;
import static java.lang.Math.ceil;
public class RowIcon extends CachingScalableJBIcon<RowIcon> {
private final Alignment myAlignment;
private int myWidth;
@@ -123,7 +127,7 @@ public class RowIcon extends JBUI.UpdatingScalableJBIcon<RowIcon> {
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
if (updateJBUIScale()) updateSize();
if (getScaleContext().update()) updateSize();
int _x = x;
int _y = y;
for (Icon icon : myScaledIcons()) {
@@ -144,14 +148,14 @@ public class RowIcon extends JBUI.UpdatingScalableJBIcon<RowIcon> {
@Override
public int getIconWidth() {
if (updateJBUIScale()) updateSize();
return scaleVal(myWidth, Scale.INSTANCE);
if (getScaleContext().update()) updateSize();
return (int)ceil(scaleVal(myWidth, OBJ_SCALE));
}
@Override
public int getIconHeight() {
if (updateJBUIScale()) updateSize();
return scaleVal(myHeight, Scale.INSTANCE);
if (getScaleContext().update()) updateSize();
return (int)ceil(scaleVal(myHeight, OBJ_SCALE));
}
private void updateSize() {
@@ -30,8 +30,8 @@ import com.intellij.ui.JBColor;
import com.intellij.ui.LayeredIcon;
import com.intellij.ui.RowIcon;
import com.intellij.util.ui.*;
import com.intellij.util.ui.JBUI.JBUIScaleUpdatable;
import com.intellij.util.ui.JBUI.ScaleType;
import com.intellij.util.ui.JBUI.ScaleContext;
import com.intellij.util.ui.JBUI.ScaleContextAware;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -40,6 +40,9 @@ import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import static com.intellij.util.ui.JBUI.ScaleType.USR_SCALE;
import static java.lang.Math.ceil;
/**
* @author max
@@ -69,28 +72,35 @@ public class IconUtil {
return icon;
}
final int w = Math.min(icon.getIconWidth(), maxWidth);
final int h = Math.min(icon.getIconHeight(), maxHeight);
Image image = toImage(icon);
if (image == null) return icon;
final BufferedImage image = GraphicsEnvironment
.getLocalGraphicsEnvironment()
.getDefaultScreenDevice()
.getDefaultConfiguration()
.createCompatibleImage(icon.getIconWidth(), icon.getIconHeight(), Transparency.TRANSLUCENT);
final Graphics2D g = image.createGraphics();
icon.paintIcon(new JPanel(), g, 0, 0);
g.dispose();
double scale = 1f;
if (image instanceof JBHiDPIScaledImage) {
scale = ((JBHiDPIScaledImage)image).getScale();
image = ((JBHiDPIScaledImage)image).getDelegate();
}
BufferedImage bi = ImageUtil.toBufferedImage(image);
final Graphics2D g = bi.createGraphics();
int imageWidth = ImageUtil.getRealWidth(image);
int imageHeight = ImageUtil.getRealHeight(image);
final int w = Math.min(imageWidth, maxWidth);
final int h = Math.min(imageHeight, maxHeight);
maxWidth = maxWidth == Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)ceil(maxWidth * scale);
maxHeight = maxHeight == Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)ceil(maxHeight * scale);
final BufferedImage img = UIUtil.createImage(g, w, h, Transparency.TRANSLUCENT);
final int offX = icon.getIconWidth() > maxWidth ? (icon.getIconWidth() - maxWidth) / 2 : 0;
final int offY = icon.getIconHeight() > maxHeight ? (icon.getIconHeight() - maxHeight) / 2 : 0;
final int offX = imageWidth > maxWidth ? (imageWidth - maxWidth) / 2 : 0;
final int offY = imageHeight > maxHeight ? (imageHeight - maxHeight) / 2 : 0;
for (int col = 0; col < w; col++) {
for (int row = 0; row < h; row++) {
img.setRGB(col, row, image.getRGB(col + offX, row + offY));
img.setRGB(col, row, bi.getRGB(col + offX, row + offY));
}
}
return new ImageIcon(img);
g.dispose();
return new JBImageIcon(RetinaImage.createFrom(img, scale, null));
}
@NotNull
@@ -466,8 +476,8 @@ public class IconUtil {
@NotNull
public static Icon scale(@NotNull Icon icon, @Nullable Component ancestor, float scale) {
if (icon instanceof ScalableIcon) {
if (icon instanceof JBUI.JBUIScaleUpdatable) {
((JBUI.JBUIScaleUpdatable)icon).updateJBUIScale(ancestor != null ? ancestor.getGraphicsConfiguration() : null);
if (icon instanceof ScaleContextAware) {
((ScaleContextAware)icon).updateScaleContext(ancestor != null ? ScaleContext.create(ancestor) : null);
}
return ((ScalableIcon)icon).scale(scale);
}
@@ -491,11 +501,11 @@ public class IconUtil {
public static Icon scaleByFont(@NotNull Icon icon, @Nullable Component ancestor, float fontSize) {
float scale = JBUI.getFontScale(fontSize);
if (icon instanceof ScalableIcon) {
if (icon instanceof JBUIScaleUpdatable) {
JBUI.JBUIScaleUpdatable jbuiIcon = (JBUI.JBUIScaleUpdatable)icon;
jbuiIcon.updateJBUIScale(ancestor != null ? ancestor.getGraphicsConfiguration() : null);
if (icon instanceof ScaleContextAware) {
ScaleContextAware ctxIcon = (ScaleContextAware)icon;
ctxIcon.updateScaleContext(ancestor != null ? ScaleContext.create(ancestor) : null);
// take into account the user scale of the icon
float usrScale = jbuiIcon.getJBUIScale(ScaleType.USR);
double usrScale = ctxIcon.getScaleContext().getScale(USR_SCALE);
scale /= usrScale;
}
return ((ScalableIcon)icon).scale(scale);
@@ -39,6 +39,8 @@ import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import static java.lang.Math.ceil;
public class FileStatusColorsTable extends JBTable {
private JBPopupMenu mySetColorMenu;
@@ -269,7 +271,7 @@ public class FileStatusColorsTable extends JBTable {
final int iconWidth = getIconWidth();
g.setColor(myColor);
final int size = scaleVal(COLOR_HEIGHT);
final int size = (int)ceil(scaleVal(COLOR_HEIGHT));
final int y = j + (iconHeight - size) / 2;
g.fillRect(i, y, iconWidth, size);
@@ -54,6 +54,7 @@ import com.intellij.util.IconUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.PlatformIcons;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.JBUI.ScaleType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -66,6 +67,9 @@ import java.awt.geom.Rectangle2D;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import static com.intellij.util.ui.JBUI.ScaleType.OBJ_SCALE;
import static java.lang.Math.ceil;
public class Bookmark implements Navigatable, Comparable<Bookmark> {
public static final Icon DEFAULT_ICON = new MyCheckedIcon();
@@ -384,12 +388,12 @@ public class Bookmark implements Navigatable, Comparable<Bookmark> {
@Override
public int getIconWidth() {
return scaleVal(DEFAULT_ICON.getIconWidth(), Scale.INSTANCE);
return (int)ceil(scaleVal(DEFAULT_ICON.getIconWidth(), OBJ_SCALE));
}
@Override
public int getIconHeight() {
return scaleVal(DEFAULT_ICON.getIconHeight(), Scale.INSTANCE);
return (int)ceil(scaleVal(DEFAULT_ICON.getIconHeight(), OBJ_SCALE));
}
@Override
@@ -422,12 +426,12 @@ public class Bookmark implements Navigatable, Comparable<Bookmark> {
@Override
public int getIconWidth() {
return scaleVal(PlatformIcons.CHECK_ICON.getIconWidth(), Scale.INSTANCE);
return (int)ceil(scaleVal(PlatformIcons.CHECK_ICON.getIconWidth(), OBJ_SCALE));
}
@Override
public int getIconHeight() {
return scaleVal(PlatformIcons.CHECK_ICON.getIconHeight(), Scale.INSTANCE);
return (int)ceil(scaleVal(PlatformIcons.CHECK_ICON.getIconHeight(), OBJ_SCALE));
}
@NotNull
@@ -34,6 +34,7 @@ import com.intellij.util.concurrency.AppExecutorUtil;
import com.intellij.util.containers.TransferToEDTQueue;
import com.intellij.util.ui.EmptyIcon;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.JBUI.CachingScalableJBIcon;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -46,7 +47,7 @@ import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.Executor;
public class DeferredIconImpl<T> extends JBUI.UpdatingScalableJBIcon<DeferredIconImpl<T>> implements DeferredIcon, RetrievableIcon {
public class DeferredIconImpl<T> extends CachingScalableJBIcon<DeferredIconImpl<T>> 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();
@@ -90,11 +91,12 @@ public class DeferredIconImpl<T> extends JBUI.UpdatingScalableJBIcon<DeferredIco
}
@Override
public void setScale(float scale) {
public Icon scale(float scale) {
if (getScale() != scale && myDelegateIcon instanceof ScalableIcon) {
myScaledDelegateIcon = ((ScalableIcon)myDelegateIcon).scale(scale);
super.setScale(scale);
super.scale(scale);
}
return this;
}
private static class Holder {
@@ -18,10 +18,7 @@ package com.intellij.ui;
import com.intellij.openapi.ui.popup.IconButton;
import com.intellij.openapi.util.Pass;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.util.ui.BaseButtonBehavior;
import com.intellij.util.ui.CenteredIcon;
import com.intellij.util.ui.TimedDeadzone;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.*;
import javax.accessibility.*;
import javax.swing.*;
@@ -48,6 +45,8 @@ public class InplaceButton extends JComponent implements ActiveComponent, Access
private int myYTransform = 0;
private boolean myFill;
private JBDimension mySize;
private boolean myHoveringEnabled;
public InplaceButton(String tooltip, final Icon icon, final ActionListener listener) {
@@ -129,7 +128,11 @@ public class InplaceButton extends JComponent implements ActiveComponent, Access
height = Math.max(height, hovered.getIconHeight());
setPreferredSize(new Dimension(width, height));
JBDimension size = JBDimension.create(new Dimension(width, height), true);
if (mySize != null && !mySize.size().equals(size)) {
invalidate();
}
mySize = size;
myIcon = regular;
myRegular = new CenteredIcon(regular, width, height);
@@ -137,6 +140,12 @@ public class InplaceButton extends JComponent implements ActiveComponent, Access
myInactive = new CenteredIcon(inactive, width, height);
}
@Override
public Dimension getPreferredSize() {
if (mySize == null || isPreferredSizeSet()) return super.getPreferredSize();
return mySize.size();
}
public InplaceButton setFillBg(boolean fill) {
myFill = fill;
return this;
@@ -22,6 +22,9 @@ import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import static java.lang.Math.ceil;
import static java.lang.Math.floor;
/**
* @author peter
*/
@@ -67,10 +70,10 @@ public class SizedIcon extends JBUI.CachingScalableJBIcon {
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
Icon icon = myScaledIcon();
int dx = scaleVal(myWidth) - icon.getIconWidth();
int dy = scaleVal(myHeight) - icon.getIconHeight();
double dx = scaleVal(myWidth) - icon.getIconWidth();
double dy = scaleVal(myHeight) - icon.getIconHeight();
if (dx > 0 || dy > 0) {
icon.paintIcon(c, g, x + dx / 2, y + dy / 2);
icon.paintIcon(c, g, x + (int)floor(dx / 2), y + (int)floor(dy / 2));
}
else {
icon.paintIcon(c, g, x, y);
@@ -78,10 +81,10 @@ public class SizedIcon extends JBUI.CachingScalableJBIcon {
}
public int getIconWidth() {
return scaleVal(myWidth);
return (int)ceil(scaleVal(myWidth));
}
public int getIconHeight() {
return scaleVal(myHeight);
return (int)ceil(scaleVal(myHeight));
}
}
@@ -449,7 +449,7 @@ public class ActionToolbarImpl extends JPanel implements ActionToolbar, QuickAct
}
} : myButtonLook,
myPlace, myPresentationFactory.getPresentation(action),
myMinimumButtonSize);
myMinimumButtonSize.size());
}
@Override
@@ -721,7 +721,7 @@ public class ActionToolbarImpl extends JPanel implements ActionToolbar, QuickAct
int xOffset = 0;
int yOffset = 0;
// Calculate max size of a row. It's not possible to make more then 3 column toolbar
final int maxRowHeight = Math.max(heightToFit, componentCount * myMinimumButtonSize.height / 3);
final int maxRowHeight = Math.max(heightToFit, componentCount * myMinimumButtonSize.height() / 3);
for (int i = 0; i < componentCount; i++) {
if (yOffset + maxHeight > maxRowHeight) { // place component at new row
yOffset = 0;
@@ -750,7 +750,7 @@ public class ActionToolbarImpl extends JPanel implements ActionToolbar, QuickAct
int xOffset = 0;
int yOffset = 0;
// Calculate max size of a row. It's not possible to make more then 3 row toolbar
final int maxRowWidth = Math.max(widthToFit, componentCount * myMinimumButtonSize.width / 3);
final int maxRowWidth = Math.max(widthToFit, componentCount * myMinimumButtonSize.width() / 3);
for (int i = 0; i < componentCount; i++) {
final Dimension d = dims[i];
if (xOffset + d.width > maxRowWidth) { // place component at new row
@@ -778,7 +778,7 @@ public class ActionToolbarImpl extends JPanel implements ActionToolbar, QuickAct
int xOffset = 0;
int yOffset = 0;
// Calculate max size of a row. It's not possible to make more then 3 column toolbar
final int maxRowHeight = Math.max(heightToFit, componentCount * myMinimumButtonSize.height / 3);
final int maxRowHeight = Math.max(heightToFit, componentCount * myMinimumButtonSize.height() / 3);
for (int i = 0; i < componentCount; i++) {
final Dimension d = dims[i];
if (yOffset + d.height > maxRowHeight) { // place component at new row
@@ -841,7 +841,6 @@ public class ActionToolbarImpl extends JPanel implements ActionToolbar, QuickAct
@Override
public Dimension getPreferredSize() {
final ArrayList<Rectangle> bounds = new ArrayList<>();
if (myMinimumButtonSize != null) myMinimumButtonSize.update();
calculateBounds(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE), bounds);
if (bounds.isEmpty()) return JBUI.emptySize();
int xLeft = Integer.MAX_VALUE;
@@ -882,7 +881,7 @@ public class ActionToolbarImpl extends JPanel implements ActionToolbar, QuickAct
}
if (myLayoutPolicy == AUTO_LAYOUT_POLICY) {
final Insets i = getInsets();
return new Dimension(AllIcons.Ide.Link.getIconWidth() + i.left + i.right, myMinimumButtonSize.height + i.top + i.bottom);
return new Dimension(AllIcons.Ide.Link.getIconWidth() + i.left + i.right, myMinimumButtonSize.height() + i.top + i.bottom);
}
else {
return super.getMinimumSize();
@@ -929,8 +928,7 @@ public class ActionToolbarImpl extends JPanel implements ActionToolbar, QuickAct
@Override
public Dimension getPreferredSize() {
mySize.update();
return mySize;
return mySize.size();
}
@Override
@@ -975,7 +973,7 @@ public class ActionToolbarImpl extends JPanel implements ActionToolbar, QuickAct
@Override
public void setMinimumButtonSize(@NotNull final Dimension size) {
myMinimumButtonSize = JBDimension.create(size, false);
myMinimumButtonSize = JBDimension.create(size, true);
for (int i = getComponentCount() - 1; i >= 0; i--) {
final Component component = getComponent(i);
if (component instanceof ActionButton) {
@@ -16,6 +16,9 @@
package com.intellij.openapi.ui.impl;
import com.intellij.util.IconUtil;
import com.intellij.util.ui.JBUI.ScaleContext;
import com.intellij.util.ui.JBUI.ScaleContextSupport;
import com.intellij.util.ui.JBUI.ScaleContextAware;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
@@ -25,7 +28,7 @@ import java.awt.image.BufferedImage;
/**
* @author Konstantin Bulenkov
*/
public class ShadowPainter {
public class ShadowPainter extends ScaleContextSupport<ScaleContext> {
private final Icon myTop;
private final Icon myTopRight;
private final Icon myRight;
@@ -34,18 +37,27 @@ public class ShadowPainter {
private final Icon myBottomLeft;
private final Icon myLeft;
private final Icon myTopLeft;
private Icon myCroppedTop = null;
private Icon myCroppedRight = null;
private Icon myCroppedBottom = null;
private Icon myCroppedLeft = null;
@Nullable
private Color myBorderColor;
public ShadowPainter(Icon top, Icon topRight, Icon right, Icon bottomRight, Icon bottom, Icon bottomLeft, Icon left, Icon topLeft) {
myTop = IconUtil.cropIcon(top, 1, Integer.MAX_VALUE);
super(ScaleContext.create());
myTop = top;
myTopRight = topRight;
myRight = IconUtil.cropIcon(right, Integer.MAX_VALUE, 1);
myRight = right;
myBottomRight = bottomRight;
myBottom = IconUtil.cropIcon(bottom, 1, Integer.MAX_VALUE);
myBottom = bottom;
myBottomLeft = bottomLeft;
myLeft = IconUtil.cropIcon(left, Integer.MAX_VALUE, 1);
myLeft = left;
myTopLeft = topLeft;
updateIcons(null);
}
public ShadowPainter(Icon top, Icon topRight, Icon right, Icon bottomRight, Icon bottom, Icon bottomLeft, Icon left, Icon topLeft, @Nullable Color borderColor) {
@@ -58,10 +70,7 @@ public class ShadowPainter {
}
public BufferedImage createShadow(final JComponent c, final int width, final int height) {
final GraphicsConfiguration graphicsConfiguration = GraphicsEnvironment.getLocalGraphicsEnvironment().
getDefaultScreenDevice().getDefaultConfiguration();
final BufferedImage image = graphicsConfiguration.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
final BufferedImage image = c.getGraphicsConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT);
final Graphics2D g = image.createGraphics();
paintShadow(c, g, 0, 0, width, height);
@@ -70,12 +79,31 @@ public class ShadowPainter {
return image;
}
public void paintShadow(Component c, Graphics2D g, int x, int y, int width, int height) {
final int leftSize = myLeft.getIconWidth();
final int rightSize = myRight.getIconWidth();
final int bottomSize = myBottom.getIconHeight();
final int topSize = myTop.getIconHeight();
private void updateIcons(ScaleContext ctx) {
updateIcon(myTop, ctx, () -> myCroppedTop = IconUtil.cropIcon(myTop, 1, Integer.MAX_VALUE));
updateIcon(myTopRight, ctx, null);
updateIcon(myRight, ctx, () -> myCroppedRight = IconUtil.cropIcon(myRight, Integer.MAX_VALUE, 1));
updateIcon(myBottomRight, ctx, null);
updateIcon(myBottom, ctx, () -> myCroppedBottom = IconUtil.cropIcon(myBottom, 1, Integer.MAX_VALUE));
updateIcon(myBottomLeft, ctx, null);
updateIcon(myLeft, ctx, () -> myCroppedLeft = IconUtil.cropIcon(myLeft, Integer.MAX_VALUE, 1));
updateIcon(myTopLeft, ctx, null);
}
private void updateIcon(Icon icon, ScaleContext ctx, Runnable r) {
if (icon instanceof ScaleContextAware) ((ScaleContextAware)icon).updateScaleContext(ctx);
if (r != null) r.run();
}
public void paintShadow(Component c, Graphics2D g, int x, int y, int width, int height) {
ScaleContext ctx = ScaleContext.create(c);
if (updateScaleContext(ctx)) {
updateIcons(ctx);
}
final int leftSize = myCroppedLeft.getIconWidth();
final int rightSize = myCroppedRight.getIconWidth();
final int bottomSize = myCroppedBottom.getIconHeight();
final int topSize = myCroppedTop.getIconHeight();
myTopLeft.paintIcon(c, g, x, y);
myTopRight.paintIcon(c, g, x + width - myTopRight.getIconWidth(), y);
@@ -83,16 +111,16 @@ public class ShadowPainter {
myBottomLeft.paintIcon(c, g, x, y + height - myBottomLeft.getIconHeight());
for (int _x = myTopLeft.getIconWidth(); _x < width - myTopRight.getIconWidth(); _x++) {
myTop.paintIcon(c, g, _x + x, y);
myCroppedTop.paintIcon(c, g, _x + x, y);
}
for (int _x = myBottomLeft.getIconWidth(); _x < width - myBottomLeft.getIconWidth(); _x++) {
myBottom.paintIcon(c, g, _x + x, y + height - bottomSize);
myCroppedBottom.paintIcon(c, g, _x + x, y + height - bottomSize);
}
for (int _y = myTopLeft.getIconHeight(); _y < height - myBottomLeft.getIconHeight(); _y++) {
myLeft.paintIcon(c, g, x, _y + y);
myCroppedLeft.paintIcon(c, g, x, _y + y);
}
for (int _y = myTopRight.getIconHeight(); _y < height - myBottomRight.getIconHeight(); _y++) {
myRight.paintIcon(c, g, x + width - rightSize, _y + y);
myCroppedRight.paintIcon(c, g, x + width - rightSize, _y + y);
}
if (myBorderColor != null) {
@@ -40,7 +40,7 @@ public final class FontComboBox extends ComboBox {
private static final FontInfoRenderer RENDERER = new FontInfoRenderer();
private Model myModel;
private JBDimension myPrefSize;
private final JBDimension mySize;
public FontComboBox() {
this(false);
@@ -52,9 +52,10 @@ public final class FontComboBox extends ComboBox {
public FontComboBox(boolean withAllStyles, boolean filterNonLatin, boolean noFontItem) {
super(new Model(withAllStyles, filterNonLatin, noFontItem));
Dimension size = getPreferredSize();
Dimension size = super.getPreferredSize();
size.width = size.height * 8;
myPrefSize = JBDimension.create(size, false);
// preScaled=true as 'size' reflects already scaled font
mySize = JBDimension.create(size, true);
setSwingPopup(true);
setRenderer(RENDERER);
getModel().addListDataListener(new ListDataListener() {
@@ -77,9 +78,8 @@ public final class FontComboBox extends ComboBox {
@Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet() || myPrefSize == null) return super.getPreferredSize();
myPrefSize.update();
return myPrefSize;
if (isPreferredSizeSet()) return super.getPreferredSize();
return mySize.size();
}
public boolean isMonospacedOnly() {
@@ -21,15 +21,14 @@ import com.intellij.openapi.util.registry.RegistryValue;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.reference.SoftReference;
import com.intellij.ui.RetrievableIcon;
import com.intellij.util.ConcurrencyUtil;
import com.intellij.util.ImageLoader;
import com.intellij.util.ReflectionUtil;
import com.intellij.util.RetinaImage;
import com.intellij.util.*;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.ImageUtil;
import com.intellij.util.ui.JBImageIcon;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.JBUI.ScaleType;
import com.intellij.util.ui.JBUI.ScaleContext;
import com.intellij.util.ui.JBUI.RasterJBIcon;
import com.intellij.util.ui.JBUI.BaseScaleContext.UpdateListener;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -47,6 +46,8 @@ import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentMap;
import static com.intellij.util.ui.JBUI.ScaleType.*;
public final class IconLoader {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.IconLoader");
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
@@ -380,7 +381,7 @@ public final class IconLoader {
return icon;
}
private static final class CachedImageIcon extends JBUI.UpdatingJBIcon implements ScalableIcon {
private static final class CachedImageIcon extends RasterJBIcon implements ScalableIcon {
private volatile Object myRealIcon;
private String myOriginalPath;
private ClassLoader myClassLoader;
@@ -393,6 +394,16 @@ public final class IconLoader {
private ImageFilter[] myFilters;
private final MyScaledIconsCache myScaledIconsCache = new MyScaledIconsCache();
{
// For instance, ShadowPainter updates the context from outside.
getScaleContext().addUpdateListener(new UpdateListener() {
@Override
public void contextUpdated() {
myRealIcon = null;
}
});
}
private CachedImageIcon(@NotNull CachedImageIcon icon) {
myRealIcon = null; // to be computed
myOriginalPath = icon.myOriginalPath;
@@ -419,29 +430,22 @@ public final class IconLoader {
return myFilters[0];
}
@Override
public boolean updateJBUIScale(Graphics2D g) {
if (needUpdateJBUIScale(g)) {
getRealIcon(g); // force update
return true;
}
return false;
}
@NotNull
private synchronized ImageIcon getRealIcon() {
return getRealIcon(null);
}
@NotNull
private synchronized ImageIcon getRealIcon(@Nullable Graphics g) {
if (!isValid() || needUpdateJBUIScale((Graphics2D)g)) {
private synchronized ImageIcon getRealIcon(ScaleContext ctx) {
if (updateScaleContext(ctx)) {
myRealIcon = null;
}
if (!isValid()) {
if (isLoaderDisabled()) return EMPTY_ICON;
myRealIcon = null;
dark = USE_DARK_ICONS;
super.updateJBUIScale((Graphics2D)g);
setGlobalFilter(IMAGE_FILTER);
if (!isValid()) myScaledIconsCache.clear();
myScaledIconsCache.clear();
if (numberOfPatchers != ourPatchers.size()) {
numberOfPatchers = ourPatchers.size();
Pair<String, Class> patchedPath = patchPath(myOriginalPath);
@@ -467,7 +471,7 @@ public final class IconLoader {
if (icon != null) return icon;
}
icon = myScaledIconsCache.getOrLoadIcon(getJBUIScale(ScaleType.PIX));
icon = myScaledIconsCache.getOrScaleIcon(1f);
if (icon != null) {
if (icon.getIconWidth() < 50 && icon.getIconHeight() < 50) {
@@ -488,7 +492,10 @@ public final class IconLoader {
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
getRealIcon(g).paintIcon(c, g, x, y);
// Component is preferable to Graphics as a scale provider, as it lets the context stick
// to the comp's actual scale via the update method.
ScaleContext ctx = c != null ? ScaleContext.create(c) : ScaleContext.create((Graphics2D)g);
getRealIcon(ctx).paintIcon(c, g, x, y);
}
@Override
@@ -517,7 +524,7 @@ public final class IconLoader {
getRealIcon(); // force state update & cache reset
Icon icon = myScaledIconsCache.getOrScaleIcon(getJBUIScale(ScaleType.PIX), scale);
Icon icon = myScaledIconsCache.getOrScaleIcon(scale);
if (icon != null) {
return icon;
}
@@ -530,85 +537,58 @@ public final class IconLoader {
return icon;
}
private class MyScaledIconsCache {
// Map {false -> image}, {true -> image@2x}
private Map<Boolean, SoftReference<Image>> origImagesCache = Collections.synchronizedMap(new HashMap<Boolean, SoftReference<Image>>(2));
private Image loadFromUrl(ScaleContext ctx) {
return ImageLoader.loadFromUrl(myUrl, true, myFilters, ctx);
}
private class MyScaledIconsCache {
private static final int SCALED_ICONS_CACHE_LIMIT = 5;
// Map {pixel scale -> icon}
private Map<Float, SoftReference<ImageIcon>> scaledIconsCache = Collections.synchronizedMap(new LinkedHashMap<Float, SoftReference<ImageIcon>>(SCALED_ICONS_CACHE_LIMIT) {
private Map<Double, SoftReference<ImageIcon>> scaledIconsCache = Collections.synchronizedMap(new LinkedHashMap<Double, SoftReference<ImageIcon>>(SCALED_ICONS_CACHE_LIMIT) {
@Override
public boolean removeEldestEntry(Map.Entry<Float, SoftReference<ImageIcon>> entry) {
public boolean removeEldestEntry(Map.Entry<Double, SoftReference<ImageIcon>> entry) {
return size() > SCALED_ICONS_CACHE_LIMIT;
}
});
/**
* Retrieves the orig image (1x, 2x) based on the pixScale.
* Retrieves the orig icon scaled by the provided scale.
*/
private Image getOrLoadOrigImage(boolean needRetinaImage) {
Image image = SoftReference.dereference(origImagesCache.get(needRetinaImage));
if (image != null) return image;
public ImageIcon getOrScaleIcon(final float scale) {
updateScale(OBJ_SCALE.of(scale));
image = ImageLoader.loadFromUrl(myUrl, false, myFilters, needRetinaImage ? 2f : 1f);
if (image == null) return null;
origImagesCache.put(needRetinaImage, new SoftReference<Image>(image));
return image;
}
/**
* Retrieves the orig icon based on the pixScale, then scale it by the instanceScale.
*/
public ImageIcon getOrScaleIcon(float pixScale, float instanceScale) {
final float effectiveScale = pixScale * instanceScale;
ImageIcon icon = SoftReference.dereference(scaledIconsCache.get(effectiveScale));
ImageIcon icon = SoftReference.dereference(scaledIconsCache.get(getScale(PIX_SCALE)));
if (icon != null) {
return icon;
}
Image image;
if (svg) {
image = doWithTmpRegValue("ide.svg.icon", true, new Callable<Image>() {
@Override
public Image call() {
return ImageLoader.loadFromUrl(myUrl, true, myFilters, effectiveScale);
return loadFromUrl(getScaleContext());
}
});
}
else {
boolean needRetinaImage = JBUI.isHiDPI(effectiveScale);
image = getOrLoadOrigImage(needRetinaImage);
if (image == null) return null;
if (!UIUtil.isJreHiDPIEnabled() && needRetinaImage) {
instanceScale = effectiveScale / 2f; // the image is 2x raw BufferedImage, compensate it
}
image = ImageUtil.scaleImage(image, instanceScale);
image = loadFromUrl(getScaleContext());
}
icon = checkIcon(image, myUrl);
if (icon != null && (icon.getIconWidth() * icon.getIconHeight() * 4) < ImageLoader.CACHED_IMAGE_MAX_SIZE) {
scaledIconsCache.put(effectiveScale, new SoftReference<ImageIcon>(icon));
scaledIconsCache.put(getScale(PIX_SCALE), new SoftReference<ImageIcon>(icon));
}
return icon;
}
/**
* Retrieves the orig icon based on the pixScale.
*/
public ImageIcon getOrLoadIcon(float pixScale) {
return getOrScaleIcon(pixScale, 1f);
}
public void clear() {
scaledIconsCache.clear();
origImagesCache.clear();
}
}
}
public abstract static class LazyIcon extends JBUI.UpdatingJBIcon {
public abstract static class LazyIcon extends RasterJBIcon {
private boolean myWasComputed;
private Icon myIcon;
private boolean isDarkVariant = USE_DARK_ICONS;
@@ -617,7 +597,10 @@ public final class IconLoader {
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
final Icon icon = getOrComputeIcon(g);
if (updateScaleContext(ScaleContext.create((Graphics2D)g))) {
myIcon = null;
}
final Icon icon = getOrComputeIcon();
if (icon != null) {
icon.paintIcon(c, g, x, y);
}
@@ -636,13 +619,11 @@ public final class IconLoader {
}
protected final synchronized Icon getOrComputeIcon() {
return getOrComputeIcon(null);
}
protected final synchronized Icon getOrComputeIcon(@Nullable Graphics g) {
if (!myWasComputed || isDarkVariant != USE_DARK_ICONS || needUpdateJBUIScale((Graphics2D)g) || filter != IMAGE_FILTER || numberOfPatchers != ourPatchers.size()) {
if (!myWasComputed || isDarkVariant != USE_DARK_ICONS ||
myIcon == null ||
filter != IMAGE_FILTER || numberOfPatchers != ourPatchers.size())
{
isDarkVariant = USE_DARK_ICONS;
updateJBUIScale((Graphics2D)g);
filter = IMAGE_FILTER;
myWasComputed = true;
numberOfPatchers = ourPatchers.size();
@@ -662,7 +643,7 @@ public final class IconLoader {
Icon icon = getOrComputeIcon();
if (icon != null) {
if (icon instanceof CachedImageIcon) {
Image img = ((CachedImageIcon)icon).myScaledIconsCache.getOrLoadOrigImage(false);
Image img = ((CachedImageIcon)icon).loadFromUrl(ScaleContext.create(USR_SCALE.of(1d), SYS_SCALE.of(1d)));
if (img != null) {
icon = new ImageIcon(img);
}
@@ -25,6 +25,7 @@ import com.intellij.openapi.util.registry.Registry;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.ImageUtil;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.JBUI.ScaleContext;
import com.intellij.util.ui.UIUtil;
import org.imgscalr.Scalr;
import org.jetbrains.annotations.NonNls;
@@ -44,6 +45,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import static com.intellij.util.ui.JBUI.ScaleType.*;
public class ImageLoader implements Serializable {
private static final Logger LOG = Logger.getInstance("#com.intellij.util.ImageLoader");
@@ -56,29 +59,29 @@ public class ImageLoader implements Serializable {
SVG {
@Override
public Image load(URL url, InputStream is, float scale) throws IOException {
public Image load(URL url, InputStream is, double scale) throws IOException {
return SVGLoader.load(url, is, scale);
}
},
UNDEFINED;
public Image load(URL url, InputStream stream, float scale) throws IOException {
return ImageLoader.load(stream, (int)scale);
public Image load(URL url, InputStream stream, double scale) throws IOException {
return ImageLoader.load(stream, scale);
}
}
public final String path;
public final @Nullable Class cls; // resource class if present
public final float scale; // initial scale factor
public final double scale; // initial scale factor
public final Type type;
public final boolean original; // path is not altered
public ImageDesc(String path, Class cls, float scale, Type type) {
public ImageDesc(String path, Class cls, double scale, Type type) {
this(path, cls, scale, type, false);
}
public ImageDesc(String path, Class cls, float scale, Type type, boolean original) {
public ImageDesc(String path, Class cls, double scale, Type type, boolean original) {
this.path = path;
this.cls = cls;
this.scale = scale;
@@ -150,50 +153,44 @@ public class ImageLoader implements Serializable {
public static ImageDescList create(@NotNull String file,
@Nullable Class cls,
boolean dark,
boolean retina,
boolean allowFloatScaling)
{
return create(file, cls, dark, retina, allowFloatScaling, JBUI.pixScale());
}
public static ImageDescList create(@NotNull String file,
@Nullable Class cls,
boolean dark,
boolean retina,
boolean allowFloatScaling,
float pixScale)
ScaleContext ctx)
{
ImageDescList vars = new ImageDescList();
boolean ideSvgIconSupport = Registry.is("ide.svg.icon");
// Prefer retina images for HiDPI scale, because downscaling
// retina images provides a better result than upscaling non-retina images.
boolean retina = JBUI.isHiDPI(ctx.getScale(PIX_SCALE));
if (retina || dark || ideSvgIconSupport) {
final String name = FileUtil.getNameWithoutExtension(file);
final String ext = FileUtilRt.getExtension(file);
pixScale = adjustScaleFactor(allowFloatScaling, pixScale);
double scale = adjustScaleFactor(allowFloatScaling, ctx.getScale(PIX_SCALE));
if (ideSvgIconSupport && dark) {
vars.add(new ImageDesc(name + "_dark.svg", cls, pixScale, ImageDesc.Type.SVG));
vars.add(new ImageDesc(name + "_dark.svg", cls, scale, ImageDesc.Type.SVG));
}
if (ideSvgIconSupport) {
vars.add(new ImageDesc(name + ".svg", cls, pixScale, ImageDesc.Type.SVG));
vars.add(new ImageDesc(name + ".svg", cls, scale, ImageDesc.Type.SVG));
}
if (dark && retina) {
vars.add(new ImageDesc(name + "@2x_dark." + ext, cls, 2f, ImageDesc.Type.PNG));
vars.add(new ImageDesc(name + "@2x_dark." + ext, cls, 2d, ImageDesc.Type.PNG));
}
if (dark) {
vars.add(new ImageDesc(name + "_dark." + ext, cls, 1f, ImageDesc.Type.PNG));
vars.add(new ImageDesc(name + "_dark." + ext, cls, 1d, ImageDesc.Type.PNG));
}
if (retina) {
vars.add(new ImageDesc(name + "@2x." + ext, cls, 2f, ImageDesc.Type.PNG));
vars.add(new ImageDesc(name + "@2x." + ext, cls, 2d, ImageDesc.Type.PNG));
}
}
vars.add(new ImageDesc(file, cls, 1f, ImageDesc.Type.PNG, true));
vars.add(new ImageDesc(file, cls, 1d, ImageDesc.Type.PNG, true));
return vars;
}
}
@@ -226,12 +223,12 @@ public class ImageLoader implements Serializable {
});
}
public ImageConverterChain withRetina() {
public ImageConverterChain withHiDPI(final ScaleContext ctx) {
return with(new ImageConverter() {
@Override
public Image convert(Image source, ImageDesc desc) {
if (source != null && UIUtil.isJreHiDPIEnabled() && desc.scale > 1) {
return RetinaImage.createFrom(source, (int)desc.scale, ourComponent);
if (source != null && UIUtil.isJreHiDPI(ctx)) {
return RetinaImage.createFrom(source, ctx.getScale(SYS_SCALE), ourComponent);
}
return source;
}
@@ -280,51 +277,40 @@ public class ImageLoader implements Serializable {
@Nullable
public static Image loadFromUrl(@NotNull URL url, boolean allowFloatScaling, ImageFilter filter) {
return loadFromUrl(url, allowFloatScaling, new ImageFilter[] {filter}, JBUI.pixScale());
return loadFromUrl(url, allowFloatScaling, new ImageFilter[] {filter}, ScaleContext.create());
}
/**
* Loads an image by the passed url in scale (1x, 2x, ...) possibly closed to the passed JBUI pix scale,
* then simply returns it in the JRE-managed HiDPI mode, otherwise scales the image
* according to the passed scale and returns.
* Loads an image of available resolution (1x, 2x, ...) and scales to address the provided scale context.
* Then wraps the image with {@link JBHiDPIScaledImage} if necessary.
*/
@Nullable
public static Image loadFromUrl(@NotNull URL url, boolean allowFloatScaling, ImageFilter[] filters, float pixScale) {
final float scaleFactor = adjustScaleFactor(allowFloatScaling, pixScale); // valid for Retina as well
public static Image loadFromUrl(@NotNull URL url, final boolean allowFloatScaling, ImageFilter[] filters, final ScaleContext ctx) {
// We can't check all 3rd party plugins and convince the authors to add @2x icons.
// In IDE-managed HiDPI mode with (scaleFactor > 1.0) we should scale images manually.
// Note we never scale images in JRE-managed HiDPI mode because scaling is handled by JRE.
// In IDE-managed HiDPI mode with scale > 1.0 we scale images manually.
final boolean scaleImages = (scaleFactor > 1.0f && !UIUtil.isJreHiDPIEnabled());
// Prefer retina images for HiDPI scale, because downscaling
// retina images provides a better result than upscaling non-retina images.
final boolean loadRetinaImages = JBUI.isHiDPI(scaleFactor);
return ImageDescList.create(url.toString(), null, UIUtil.isUnderDarcula(), loadRetinaImages, allowFloatScaling, pixScale).load(
return ImageDescList.create(url.toString(), null, UIUtil.isUnderDarcula(), allowFloatScaling, ctx).load(
ImageConverterChain.create().
withFilter(filters).
withRetina().
with(new ImageConverter() {
public Image convert(Image source, ImageDesc desc) {
if (source != null && scaleImages && desc.type != ImageDesc.Type.SVG) {
if (desc.path.contains("@2x"))
return scaleImage(source, scaleFactor / 2.0f); // divide by 2.0 as Retina images are 2x the resolution.
else
return scaleImage(source, scaleFactor);
if (source != null && desc.type != ImageDesc.Type.SVG) {
double scale = adjustScaleFactor(allowFloatScaling, ctx.getScale(PIX_SCALE));
if (desc.scale > 1) scale /= desc.scale; // compensate the image original scale
source = scaleImage(source, scale);
}
return source;
}
}));
}).
withHiDPI(ctx));
}
private static float adjustScaleFactor(boolean allowFloatScaling, float scale) {
private static double adjustScaleFactor(boolean allowFloatScaling, double scale) {
return allowFloatScaling ? scale : JBUI.isHiDPI(scale) ? 2f : 1f;
}
@NotNull
public static Image scaleImage(Image image, float scale) {
public static Image scaleImage(Image image, double scale) {
if (scale == 1.0) return image;
if (image instanceof JBHiDPIScaledImage) {
@@ -343,22 +329,6 @@ public class ImageLoader implements Serializable {
return Scalr.resize(ImageUtil.toBufferedImage(image), Scalr.Method.QUALITY, width, height);
}
@Nullable
public static Image loadFromUrl(URL url, boolean dark, boolean retina) {
return loadFromUrl(url, dark, retina, (ImageFilter[])null);
}
@Nullable
public static Image loadFromUrl(URL url, boolean dark, boolean retina, ImageFilter filter) {
return loadFromUrl(url, dark, retina, new ImageFilter[] {filter});
}
@Nullable
public static Image loadFromUrl(URL url, boolean dark, boolean retina, ImageFilter[] filters) {
return ImageDescList.create(url.toString(), null, dark, retina, true).
load(ImageConverterChain.create().withFilter(filters).withRetina());
}
@Nullable
public static Image loadFromResource(@NonNls @NotNull String s) {
Class callerClass = ReflectionUtil.getGrandCallerClass();
@@ -368,8 +338,9 @@ public class ImageLoader implements Serializable {
@Nullable
public static Image loadFromResource(@NonNls @NotNull String path, @NotNull Class aClass) {
return ImageDescList.create(path, aClass, UIUtil.isUnderDarcula(), JBUI.isHiDPI(JBUI.pixScale()), true).
load(ImageConverterChain.create().withRetina());
ScaleContext ctx = ScaleContext.create();
return ImageDescList.create(path, aClass, UIUtil.isUnderDarcula(), true, ctx).
load(ImageConverterChain.create().withHiDPI(ctx));
}
public static Image loadFromStream(@NotNull final InputStream inputStream) {
@@ -383,10 +354,10 @@ public class ImageLoader implements Serializable {
public static Image loadFromStream(@NotNull final InputStream inputStream, final int scale, ImageFilter filter) {
Image image = load(inputStream, scale);
ImageDesc desc = new ImageDesc("", null, scale, ImageDesc.Type.UNDEFINED);
return ImageConverterChain.create().withFilter(filter).withRetina().convert(image, desc);
return ImageConverterChain.create().withFilter(filter).withHiDPI(ScaleContext.create()).convert(image, desc);
}
private static Image load(@NotNull final InputStream inputStream, final int scale) {
private static Image load(@NotNull final InputStream inputStream, double scale) {
if (scale <= 0) throw new IllegalArgumentException("Scale must be 1 or greater");
try {
BufferExposingByteArrayOutputStream outputStream = new BufferExposingByteArrayOutputStream();
@@ -25,15 +25,24 @@ import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import static java.lang.Math.ceil;
/**
* @author Konstantin Bulenkov
* @author tav
*/
public class JBHiDPIScaledImage extends BufferedImage {
private final @Nullable Image myImage;
private final int myUserWidth;
private final int myUserHeight;
private final float myScale;
private final double myUserWidth;
private final double myUserHeight;
private final double myScale;
/**
* @see #JBHiDPIScaledImage(double, double, int)
*/
public JBHiDPIScaledImage(int width, int height, int type) {
this((double)width, (double)height, type);
}
/**
* Creates a scaled HiDPI-aware BufferedImage, targeting the system default scale.
@@ -42,10 +51,17 @@ public class JBHiDPIScaledImage extends BufferedImage {
* @param height the height in user coordinate space
* @param type the type
*/
public JBHiDPIScaledImage(int width, int height, int type) {
public JBHiDPIScaledImage(double width, double height, int type) {
this((GraphicsConfiguration)null, width, height, type);
}
/**
* @see #JBHiDPIScaledImage(Graphics2D, double, double, int)
*/
public JBHiDPIScaledImage(@Nullable Graphics2D g, int width, int height, int type) {
this(g, (double)width, (double)height, type);
}
/**
* Creates a scaled HiDPI-aware BufferedImage, targeting the graphics scale.
*
@@ -54,7 +70,7 @@ public class JBHiDPIScaledImage extends BufferedImage {
* @param height the height in user coordinate space
* @param type the type
*/
public JBHiDPIScaledImage(@Nullable Graphics2D g, int width, int height, int type) {
public JBHiDPIScaledImage(@Nullable Graphics2D g, double width, double height, int type) {
super((int)(width * JBUI.sysScale(g)), (int)(height * JBUI.sysScale(g)), type);
myImage = null;
myUserWidth = width;
@@ -62,6 +78,13 @@ public class JBHiDPIScaledImage extends BufferedImage {
myScale = JBUI.sysScale(g);
}
/**
* @see #JBHiDPIScaledImage(GraphicsConfiguration, double, double, int)
*/
public JBHiDPIScaledImage(@Nullable GraphicsConfiguration gc, int width, int height, int type) {
this(gc, (double)width, (double)height, type);
}
/**
* Creates a scaled HiDPI-aware BufferedImage, targeting the graphics config.
*
@@ -70,7 +93,7 @@ public class JBHiDPIScaledImage extends BufferedImage {
* @param height the height in user coordinate space
* @param type the type
*/
public JBHiDPIScaledImage(@Nullable GraphicsConfiguration gc, int width, int height, int type) {
public JBHiDPIScaledImage(@Nullable GraphicsConfiguration gc, double width, double height, int type) {
super((int)(width * JBUI.sysScale(gc)), (int)(height * JBUI.sysScale(gc)), type);
myImage = null;
myUserWidth = width;
@@ -78,6 +101,13 @@ public class JBHiDPIScaledImage extends BufferedImage {
myScale = JBUI.sysScale(gc);
}
/**
* @see #JBHiDPIScaledImage(Image, double, double, int)
*/
public JBHiDPIScaledImage(@NotNull Image image, int width, int height, int type) {
this(image, (double)width, (double)height, type);
}
/**
* Creates a HiDPI-aware BufferedImage wrapper for the provided scaled raw image.
* The wrapper image will represent the scaled raw image in user coordinate space.
@@ -87,7 +117,7 @@ public class JBHiDPIScaledImage extends BufferedImage {
* @param height the height in user coordinate space
* @param type the type
*/
public JBHiDPIScaledImage(@NotNull Image image, int width, int height, int type) {
public JBHiDPIScaledImage(@NotNull Image image, double width, double height, int type) {
super(1, 1, type); // a dummy wrapper
myImage = image;
myUserWidth = width;
@@ -95,7 +125,7 @@ public class JBHiDPIScaledImage extends BufferedImage {
myScale = myUserWidth > 0 ? myImage.getWidth(null) / myUserWidth : 1f;
}
public float getScale() {
public double getScale() {
return myScale;
}
@@ -105,7 +135,7 @@ public class JBHiDPIScaledImage extends BufferedImage {
* @param scaleFactor the scale factor
* @return scaled instance
*/
public JBHiDPIScaledImage scale(float scaleFactor) {
public JBHiDPIScaledImage scale(double scaleFactor) {
Image img = myImage == null ? this: myImage;
int w = (int)(scaleFactor * getRealWidth(null));
@@ -114,15 +144,16 @@ public class JBHiDPIScaledImage extends BufferedImage {
Image scaled = Scalr.resize(ImageUtil.toBufferedImage(img), Scalr.Method.QUALITY, w, h);
int newUserWidth = (int)(w / this.myScale);
int newUserHeight = (int)(h / this.myScale);
double newUserWidth = w / this.myScale;
double newUserHeight = h / this.myScale;
if (myImage != null) {
return new JBHiDPIScaledImage(scaled, newUserWidth, newUserHeight, getType());
}
JBHiDPIScaledImage newImg = new JBHiDPIScaledImage(newUserWidth, newUserHeight, getType());
Graphics2D g = newImg.createGraphics();
g.drawImage(scaled, 0, 0, newUserWidth, newUserHeight, 0, 0, scaled.getWidth(null), scaled.getHeight(null), null);
g.drawImage(scaled, 0, 0, (int)ceil(newUserWidth), (int)ceil(newUserHeight),
0, 0, scaled.getWidth(null), scaled.getHeight(null), null);
g.dispose();
return newImg;
}
@@ -182,7 +213,7 @@ public class JBHiDPIScaledImage extends BufferedImage {
* @return the width
*/
public int getUserWidth(ImageObserver observer) {
return myImage != null ? myUserWidth : (int)(super.getWidth(observer) / myScale);
return myImage != null ? (int)ceil(myUserWidth) : (int)ceil(super.getWidth(observer) / myScale);
}
/**
@@ -192,7 +223,7 @@ public class JBHiDPIScaledImage extends BufferedImage {
* @return the height
*/
public int getUserHeight(ImageObserver observer) {
return myImage != null ? myUserHeight : (int)(super.getHeight(observer) / myScale);
return myImage != null ? (int)ceil(myUserHeight) : (int)ceil(super.getHeight(observer) / myScale);
}
/**
@@ -61,11 +61,11 @@ public class RetinaImage { // [tav] todo: create HiDPIImage class
* @return the Retina-aware wrapper
*/
@NotNull
public static Image createFrom(Image image, final float scale, ImageObserver observer) {
public static Image createFrom(Image image, final double scale, ImageObserver observer) {
int w = image.getWidth(observer);
int h = image.getHeight(observer);
Image hidpi = new JBHiDPIScaledImage(image, (int)(w / scale), (int)(h / scale), BufferedImage.TYPE_INT_ARGB);
Image hidpi = new JBHiDPIScaledImage(image, w / scale, h / scale, BufferedImage.TYPE_INT_ARGB);
if (SystemInfo.isAppleJvm) {
Graphics2D g = (Graphics2D)hidpi.getGraphics();
g.scale(1f / scale, 1f / scale);
@@ -41,8 +41,8 @@ import java.util.List;
public class SVGLoader {
private TranscoderInput input;
private BufferedImage img;
private float width;
private float height;
private double width;
private double height;
private enum SizeAttr {
width,
@@ -121,7 +121,7 @@ public class SVGLoader {
return load(null, stream, scale);
}
public static Image load(@Nullable URL url, @NotNull InputStream stream , float scale) throws IOException {
public static Image load(@Nullable URL url, @NotNull InputStream stream , double scale) throws IOException {
try {
return new SVGLoader(url, stream, scale).createImage();
}
@@ -130,7 +130,7 @@ public class SVGLoader {
}
}
private SVGLoader(@Nullable URL url, InputStream stream, float scale) throws IOException {
private SVGLoader(@Nullable URL url, InputStream stream, double scale) throws IOException {
Document document = null;
String uri = null;
try {
@@ -20,6 +20,8 @@ import org.jetbrains.annotations.NotNull;
import java.awt.*;
import static java.lang.Math.ceil;
/**
* @author Konstantin Bulenkov
*/
@@ -79,7 +81,7 @@ public class ColorIcon extends EmptyIcon {
}
private int getColorSize() {
return scaleVal(myColorSize);
return (int)ceil(scaleVal(myColorSize));
}
@Override
@@ -27,6 +27,9 @@ import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.Map;
import static com.intellij.util.ui.JBUI.ScaleType.PIX_SCALE;
import static java.lang.Math.ceil;
/**
* @author max
* @author Konstantin Bulenkov
@@ -114,13 +117,13 @@ public class EmptyIcon extends JBUI.CachingScalableJBIcon<EmptyIcon> {
}
@Override
public EmptyIcon withJBUIPreScaled(boolean preScaled) {
if (myUseCache && isJBUIPreScaled() != preScaled) {
public EmptyIcon withIconPreScaled(boolean preScaled) {
if (myUseCache && isIconPreScaled() != preScaled) {
Integer key = key(width, height);
if (key != null) cache.remove(key); // rather useless to keep it in cache
return create(width, height, preScaled);
}
return (EmptyIcon)super.withJBUIPreScaled(preScaled);
return (EmptyIcon)super.withIconPreScaled(preScaled);
}
private static EmptyIcon create(int width, int height, boolean preScaled) {
@@ -128,7 +131,7 @@ public class EmptyIcon extends JBUI.CachingScalableJBIcon<EmptyIcon> {
EmptyIcon icon = key != null ? cache.get(key) : null;
if (icon == null) {
icon = new EmptyIcon(width, height, true);
icon.setJBUIPreScaled(preScaled);
icon.setIconPreScaled(preScaled);
if (key != null) cache.put(key, icon);
}
return icon;
@@ -141,12 +144,12 @@ public class EmptyIcon extends JBUI.CachingScalableJBIcon<EmptyIcon> {
@Override
public int getIconWidth() {
return scaleVal(width);
return (int)ceil(scaleVal(width));
}
@Override
public int getIconHeight() {
return scaleVal(height);
return (int)ceil(scaleVal(height));
}
@Override
@@ -160,17 +163,17 @@ public class EmptyIcon extends JBUI.CachingScalableJBIcon<EmptyIcon> {
final EmptyIcon icon = (EmptyIcon)o;
if (scaleVal(height, Scale.EFFECTIVE) != icon.scaleVal(icon.height, Scale.EFFECTIVE)) return false;
if (scaleVal(width, Scale.EFFECTIVE) != icon.scaleVal(icon.width, Scale.EFFECTIVE)) return false;
if (scaleVal(height, PIX_SCALE) != icon.scaleVal(icon.height, PIX_SCALE)) return false;
if (scaleVal(width, PIX_SCALE) != icon.scaleVal(icon.width, PIX_SCALE)) return false;
return true;
}
@Override
public int hashCode() {
int result = scaleVal(width, Scale.EFFECTIVE);
result = 31 * result + scaleVal(height, Scale.EFFECTIVE);
return result;
double result = scaleVal(width, PIX_SCALE);
result = 31 * result + scaleVal(height, PIX_SCALE);
return (int)result;
}
public EmptyIconUIResource asUIResource() {
@@ -32,13 +32,14 @@ public class ImageUtil {
public static BufferedImage toBufferedImage(@NotNull Image image, boolean inUserSize) {
if (image instanceof JBHiDPIScaledImage) {
Image img = ((JBHiDPIScaledImage)image).getDelegate();
float scale = ((JBHiDPIScaledImage)image).getScale();
JBHiDPIScaledImage jbImage = (JBHiDPIScaledImage)image;
Image img = jbImage.getDelegate();
if (img != null) {
image = img;
if (inUserSize) {
image = scaleImage(image, 1 / scale);
double scale = jbImage.getScale();
img = scaleImage(img, 1 / scale);
}
image = img;
}
}
if (image instanceof BufferedImage) {
@@ -116,7 +117,7 @@ public class ImageUtil {
/**
* Scales the image taking into account its HiDPI awareness.
*/
public static Image scaleImage(Image image, float scale) {
public static Image scaleImage(Image image, double scale) {
return ImageLoader.scaleImage(image, scale);
}
}
@@ -15,36 +15,75 @@
*/
package com.intellij.util.ui;
import com.intellij.util.ui.JBUI.Scaler;
import javax.swing.plaf.UIResource;
import java.awt.*;
import static java.lang.Math.ceil;
/**
* @author Konstantin Bulenkov
* @author tav
*/
public class JBDimension extends Dimension {
float myJBUIScale = JBUI.scale(1f);
protected Size2D size2D;
private MyScaler scaler = new MyScaler();
private static class Size2D {
double width;
double height;
Size2D(double width, double height) {
this.width = width;
this.height = height;
}
int intWidth() {
return (int)ceil(width);
}
int intHeight() {
return (int)ceil(height);
}
Size2D copy() {
return new Size2D(width, height);
}
void set(double width, double height) {
this.width = width;
this.height = height;
}
}
public JBDimension(int width, int height) {
this(width, height, true);
this(width, height, false);
}
private JBDimension(int width, int height, boolean applyScale) {
super(applyScale ? scale(width) : width, applyScale ? scale(height) : height);
public JBDimension(int width, int height, boolean preScaled) {
this((double)width, (double)height, preScaled);
}
private static int scale(int size) {
return size == -1 ? -1 : JBUI.scale(size);
private JBDimension(double width, double height, boolean preScaled) {
size2D = new Size2D(preScaled ? width : scale(width), preScaled ? height : scale(height));
set(size2D);
}
public static JBDimension create(Dimension from) {
return create(from, true);
private double scale(double size) {
return Math.max(-1, JBUI.scale((float)size));
}
public static JBDimension create(Dimension from, boolean applyScale) {
public static JBDimension create(Dimension from, boolean preScaled) {
if (from instanceof JBDimension) {
return ((JBDimension)from);
}
return new JBDimension(from.width, from.height, applyScale);
return new JBDimension(from.width, from.height, preScaled);
}
public static JBDimension create(Dimension from) {
return create(from, false);
}
public JBDimensionUIResource asUIResource() {
@@ -54,30 +93,126 @@ public class JBDimension extends Dimension {
public static class JBDimensionUIResource extends JBDimension implements UIResource {
public JBDimensionUIResource(JBDimension size) {
super(0, 0);
width = size.width;
height = size.height;
set(size.width, size.height);
size2D = size.size2D.copy();
}
}
public JBDimension withWidth(int width) {
JBDimension size = new JBDimension(0, 0);
size.width = scale(width);
size.height = height;
size.size2D.set(scale(width), size2D.height);
size.set(size.size2D.intWidth(), height);
return size;
}
public JBDimension withHeight(int height) {
JBDimension size = new JBDimension(0, 0);
size.width = width;
size.height = scale(height);
size.size2D.set(size2D.width, scale(height));
size.set(width, size.size2D.intHeight());
return size;
}
// [tav] todo: may lose precision
protected void set(int width, int height) {
this.width = width;
this.height = height;
}
protected void set(Size2D size2d) {
set(size2d.intWidth(), size2d.intHeight());
}
/**
* Updates the size according to current {@link JBUI.ScaleType#USR_SCALE} if necessary.
* @return whether the size has been updated
*/
public boolean update() {
if (!scaler.needUpdate()) return false;
size2D.set(scaler.scaleVal(size2D.width), scaler.scaleVal(size2D.height));
set(size2D);
scaler.update();
return true;
}
/**
* @return this JBDimension with updated size
*/
public JBDimension size() {
update();
return this;
}
/**
* @return new JBDimension with updated size
*/
public JBDimension newSize() {
update();
JBDimension d = new JBDimension(size2D.width, size2D.height, true);
return d;
}
/**
* @return updated width
*/
public int width() {
update();
return width;
}
/**
* @return updated height
*/
public int height() {
update();
return height;
}
/**
* @return updated double width
*/
public double width2d() {
update();
return size2D.width;
}
/**
* @return updated double height
*/
public double height2d() {
update();
return size2D.height;
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (!(obj instanceof JBDimension)) return false;
JBDimension that = (JBDimension)obj;
return size2D.equals(that.size2D);
}
}
class MyScaler extends Scaler {
public MyScaler() {
super(true);
}
@Override
protected double currentScale() {
return JBUI.scale(1f);
}
public boolean needUpdate() {
return initialScale != JBUI.scale(1f);
}
public void update() {
float scale = JBUI.scale(1f);
width = (int)(width * scale / myJBUIScale);
height = (int)(height * scale / myJBUIScale);
myJBUIScale = scale;
setPreScaled(true); // updates initialScale
}
}
File diff suppressed because it is too large Load Diff
@@ -29,6 +29,7 @@ import com.intellij.util.*;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.JBIterable;
import com.intellij.util.containers.JBTreeTraverser;
import com.intellij.util.ui.JBUI.ScaleContext;
import com.intellij.util.ui.accessibility.ScreenReader;
import org.intellij.lang.annotations.JdkConstants;
import org.intellij.lang.annotations.Language;
@@ -380,6 +381,13 @@ public class UIUtil {
return isJreHiDPI(comp != null ? comp.getGraphicsConfiguration() : null);
}
/**
* Returns whether the JRE-managed HiDPI mode is enabled and the provided system scale context is HiDPI.
*/
public static boolean isJreHiDPI(@Nullable ScaleContext ctx) {
return isJreHiDPIEnabled() && JBUI.isHiDPI(JBUI.sysScale(ctx));
}
private static Boolean jreHiDPI;
private static boolean jreHiDPI_earlierVersion;
@@ -331,9 +331,9 @@ class StructureFilterPopupComponent extends FilterPopupComponent<VcsLogFileFilte
}
@Override
public CheckboxColorIcon withJBUIPreScaled(boolean preScaled) {
mySizedIcon = (SizedIcon)mySizedIcon.withJBUIPreScaled(preScaled);
return (CheckboxColorIcon)super.withJBUIPreScaled(preScaled);
public CheckboxColorIcon withIconPreScaled(boolean preScaled) {
mySizedIcon = (SizedIcon)mySizedIcon.withIconPreScaled(preScaled);
return (CheckboxColorIcon)super.withIconPreScaled(preScaled);
}
@Override
@@ -22,6 +22,9 @@ import org.jetbrains.annotations.NotNull;
import java.awt.*;
import static java.lang.Math.ceil;
import static java.lang.Math.floor;
/**
* @author Alexander Lobas
*/
@@ -72,22 +75,22 @@ public final class ColorIcon extends EmptyIcon {
iconWidth + coloredComponent.getIpad().left + coloredComponent.getIconTextGap(), component.getHeight());
}
int x = left + (iconWidth - scaleVal(myColorSize)) / 2;
int y = top + (iconHeight - scaleVal(myColorSize)) / 2;
int x = left + (int)floor((iconWidth - scaleVal(myColorSize)) / 2);
int y = top + (int)floor((iconHeight - scaleVal(myColorSize)) / 2);
g.setColor(myColor);
g.fillRect(x, y, scaleVal(myColorSize), scaleVal(myColorSize));
g.fillRect(x, y, (int)ceil(scaleVal(myColorSize)), (int)ceil(scaleVal(myColorSize)));
if (myShowRedLine) {
Graphics2D g2d = (Graphics2D)g;
Object hint = g2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(JBColor.red);
g.drawLine(x, y + scaleVal(myColorSize), x + scaleVal(myColorSize), y);
g.drawLine(x, y + (int)floor(scaleVal(myColorSize)), x + (int)floor(scaleVal(myColorSize)), y);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, hint);
}
g.setColor(Color.BLACK);
g.drawRect(x, y, scaleVal(myColorSize), scaleVal(myColorSize));
g.drawRect(x, y, (int)ceil(scaleVal(myColorSize)), (int)ceil(scaleVal(myColorSize)));
}
}