diff --git a/platform/core-api/src/com/intellij/ui/LayeredIcon.java b/platform/core-api/src/com/intellij/ui/LayeredIcon.java index 51fd3edccffd..4f35b0dbe9e5 100644 --- a/platform/core-api/src/com/intellij/ui/LayeredIcon.java +++ b/platform/core-api/src/com/intellij/ui/LayeredIcon.java @@ -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 { +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 { 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 { } @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 { @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 { @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() { diff --git a/platform/core-api/src/com/intellij/ui/RowIcon.java b/platform/core-api/src/com/intellij/ui/RowIcon.java index 4a3fb88202bc..ade8ae4819b6 100644 --- a/platform/core-api/src/com/intellij/ui/RowIcon.java +++ b/platform/core-api/src/com/intellij/ui/RowIcon.java @@ -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 { +import static com.intellij.util.ui.JBUI.ScaleType.OBJ_SCALE; +import static java.lang.Math.ceil; + +public class RowIcon extends CachingScalableJBIcon { private final Alignment myAlignment; private int myWidth; @@ -123,7 +127,7 @@ public class RowIcon extends JBUI.UpdatingScalableJBIcon { @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 { @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() { diff --git a/platform/core-api/src/com/intellij/util/IconUtil.java b/platform/core-api/src/com/intellij/util/IconUtil.java index cf7bcd4182cf..b7cf834bab7e 100644 --- a/platform/core-api/src/com/intellij/util/IconUtil.java +++ b/platform/core-api/src/com/intellij/util/IconUtil.java @@ -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); diff --git a/platform/lang-impl/src/com/intellij/application/options/colors/fileStatus/FileStatusColorsTable.java b/platform/lang-impl/src/com/intellij/application/options/colors/fileStatus/FileStatusColorsTable.java index a387da778a62..42379efb582c 100644 --- a/platform/lang-impl/src/com/intellij/application/options/colors/fileStatus/FileStatusColorsTable.java +++ b/platform/lang-impl/src/com/intellij/application/options/colors/fileStatus/FileStatusColorsTable.java @@ -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); diff --git a/platform/lang-impl/src/com/intellij/ide/bookmarks/Bookmark.java b/platform/lang-impl/src/com/intellij/ide/bookmarks/Bookmark.java index d24ff9e7e87e..ad0e90571a60 100644 --- a/platform/lang-impl/src/com/intellij/ide/bookmarks/Bookmark.java +++ b/platform/lang-impl/src/com/intellij/ide/bookmarks/Bookmark.java @@ -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 { public static final Icon DEFAULT_ICON = new MyCheckedIcon(); @@ -384,12 +388,12 @@ public class Bookmark implements Navigatable, Comparable { @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 { @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 diff --git a/platform/lang-impl/src/com/intellij/ui/DeferredIconImpl.java b/platform/lang-impl/src/com/intellij/ui/DeferredIconImpl.java index 42f222b59705..04580a6a9045 100644 --- a/platform/lang-impl/src/com/intellij/ui/DeferredIconImpl.java +++ b/platform/lang-impl/src/com/intellij/ui/DeferredIconImpl.java @@ -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 extends JBUI.UpdatingScalableJBIcon> implements DeferredIcon, RetrievableIcon { +public class DeferredIconImpl extends 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(); @@ -90,11 +91,12 @@ public class DeferredIconImpl extends JBUI.UpdatingScalableJBIcon 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)); } } diff --git a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionToolbarImpl.java b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionToolbarImpl.java index 0e24985000e9..e98c0601f6f3 100644 --- a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionToolbarImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionToolbarImpl.java @@ -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 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) { diff --git a/platform/platform-impl/src/com/intellij/openapi/ui/impl/ShadowPainter.java b/platform/platform-impl/src/com/intellij/openapi/ui/impl/ShadowPainter.java index 9749f4abc613..a923bca17f0f 100644 --- a/platform/platform-impl/src/com/intellij/openapi/ui/impl/ShadowPainter.java +++ b/platform/platform-impl/src/com/intellij/openapi/ui/impl/ShadowPainter.java @@ -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 { 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) { diff --git a/platform/platform-impl/src/com/intellij/ui/FontComboBox.java b/platform/platform-impl/src/com/intellij/ui/FontComboBox.java index 0f4cb2507b54..73629d26ce29 100644 --- a/platform/platform-impl/src/com/intellij/ui/FontComboBox.java +++ b/platform/platform-impl/src/com/intellij/ui/FontComboBox.java @@ -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() { diff --git a/platform/util/src/com/intellij/openapi/util/IconLoader.java b/platform/util/src/com/intellij/openapi/util/IconLoader.java index ab25a3fc83b9..c73d2a66f83a 100644 --- a/platform/util/src/com/intellij/openapi/util/IconLoader.java +++ b/platform/util/src/com/intellij/openapi/util/IconLoader.java @@ -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 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> origImagesCache = Collections.synchronizedMap(new HashMap>(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> scaledIconsCache = Collections.synchronizedMap(new LinkedHashMap>(SCALED_ICONS_CACHE_LIMIT) { + private Map> scaledIconsCache = Collections.synchronizedMap(new LinkedHashMap>(SCALED_ICONS_CACHE_LIMIT) { @Override - public boolean removeEldestEntry(Map.Entry> entry) { + public boolean removeEldestEntry(Map.Entry> 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)); - 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() { @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(icon)); + scaledIconsCache.put(getScale(PIX_SCALE), new SoftReference(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); } diff --git a/platform/util/src/com/intellij/util/ImageLoader.java b/platform/util/src/com/intellij/util/ImageLoader.java index 3e99f4ad3cb7..39850a209ffa 100644 --- a/platform/util/src/com/intellij/util/ImageLoader.java +++ b/platform/util/src/com/intellij/util/ImageLoader.java @@ -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(); diff --git a/platform/util/src/com/intellij/util/JBHiDPIScaledImage.java b/platform/util/src/com/intellij/util/JBHiDPIScaledImage.java index 7a02d04d573b..b118382e8372 100644 --- a/platform/util/src/com/intellij/util/JBHiDPIScaledImage.java +++ b/platform/util/src/com/intellij/util/JBHiDPIScaledImage.java @@ -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); } /** diff --git a/platform/util/src/com/intellij/util/RetinaImage.java b/platform/util/src/com/intellij/util/RetinaImage.java index 968a5ee1ae7e..3c73aa2f5337 100644 --- a/platform/util/src/com/intellij/util/RetinaImage.java +++ b/platform/util/src/com/intellij/util/RetinaImage.java @@ -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); diff --git a/platform/util/src/com/intellij/util/SVGLoader.java b/platform/util/src/com/intellij/util/SVGLoader.java index d7e7044755e0..efb5eda364ba 100644 --- a/platform/util/src/com/intellij/util/SVGLoader.java +++ b/platform/util/src/com/intellij/util/SVGLoader.java @@ -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 { diff --git a/platform/util/src/com/intellij/util/ui/ColorIcon.java b/platform/util/src/com/intellij/util/ui/ColorIcon.java index 082881bdc0f6..ebb286b13c21 100644 --- a/platform/util/src/com/intellij/util/ui/ColorIcon.java +++ b/platform/util/src/com/intellij/util/ui/ColorIcon.java @@ -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 diff --git a/platform/util/src/com/intellij/util/ui/EmptyIcon.java b/platform/util/src/com/intellij/util/ui/EmptyIcon.java index 98d85dbcf353..b3d63e6a203c 100644 --- a/platform/util/src/com/intellij/util/ui/EmptyIcon.java +++ b/platform/util/src/com/intellij/util/ui/EmptyIcon.java @@ -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 { } @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 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 { @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 { 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() { diff --git a/platform/util/src/com/intellij/util/ui/ImageUtil.java b/platform/util/src/com/intellij/util/ui/ImageUtil.java index 38c6594e52c2..0b684b324f3b 100644 --- a/platform/util/src/com/intellij/util/ui/ImageUtil.java +++ b/platform/util/src/com/intellij/util/ui/ImageUtil.java @@ -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); } } diff --git a/platform/util/src/com/intellij/util/ui/JBDimension.java b/platform/util/src/com/intellij/util/ui/JBDimension.java index 5db891a8daff..b9029ecf4ec7 100644 --- a/platform/util/src/com/intellij/util/ui/JBDimension.java +++ b/platform/util/src/com/intellij/util/ui/JBDimension.java @@ -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 } } diff --git a/platform/util/src/com/intellij/util/ui/JBUI.java b/platform/util/src/com/intellij/util/ui/JBUI.java index d84e3002130a..129229317626 100644 --- a/platform/util/src/com/intellij/util/ui/JBUI.java +++ b/platform/util/src/com/intellij/util/ui/JBUI.java @@ -16,13 +16,11 @@ package com.intellij.util.ui; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.ScalableIcon; import com.intellij.openapi.util.SystemInfo; import com.intellij.ui.border.CustomLineBorder; import com.intellij.util.SystemProperties; -import com.intellij.util.keyFMap.KeyFMap; import com.intellij.util.ui.components.BorderLayoutPanel; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -35,6 +33,12 @@ import java.awt.*; import java.awt.image.ImageObserver; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +import static com.intellij.util.ui.JBUI.ScaleType.*; +import static java.lang.Math.round; /** * @author Konstantin Bulenkov @@ -47,6 +51,8 @@ public class JBUI { private static final PropertyChangeSupport PCS = new PropertyChangeSupport(new JBUI()); + private static final float DISCRETE_SCALE_RESOLUTION = 0.25f; + /** * The IDE supports two different HiDPI modes: * @@ -54,12 +60,12 @@ public class JBUI { * * Supported for backward compatibility until complete transition to the JRE-managed HiDPI mode happens. * In this mode there's a single coordinate space and the whole UI is scaled by the IDE guided by the - * user scale factor ({@link #USR}). + * user scale factor ({@link #USR_SCALE}). * * 2) JRE-managed HiDPI mode. * * In this mode the JRE scales graphics prior to drawing it on the device. So, there're two coordinate - * spaces: the user space and the device space. The system scale factor ({@link #SYS}) defines the + * spaces: the user space and the device space. The system scale factor ({@link #SYS_SCALE}) defines the * transform b/w the spaces. The UI size metrics (windows, controls, fonts height) are in the user * coordinate space. Though, the raster images should be aware of the device scale in order to meet * HiDPI. (For instance, JRE on a Mac Retina monitor device works in the JRE-managed HiDPI mode, @@ -67,9 +73,10 @@ public class JBUI { * * The IDE operates the scale factors of the following types: * - * 1) The user scale factor: {@link #USR} - * 2) The system (monitor device) scale factor: {@link #SYS} - * 3) The pixel scale factor: {@link #PIX} + * 1) The user scale factor: {@link #USR_SCALE} + * 2) The system (monitor device) scale factor: {@link #SYS_SCALE} + * 3) The object (UI instance specific) scale factor: {@link #OBJ_SCALE} + * 4) The pixel scale factor: {@link #PIX_SCALE} * * @see UIUtil#isJreHiDPIEnabled() * @see UIUtil#isJreHiDPI() @@ -82,6 +89,7 @@ public class JBUI { * @see UIUtil#createImage(Graphics, int, int, int) * @see UIUtil#createImage(GraphicsConfiguration, int, int, int) * @see UIUtil#createImage(int, int, int) + * @see ScaleContext */ public enum ScaleType { /** @@ -102,7 +110,7 @@ public class JBUI { * @see #scale(float) * @see #scale(int) */ - USR, + USR_SCALE, /** * The system scale factor is defined by the device DPI and/or the system settings. For instance, * Mac Retina monitor device has the system scale 2.0 by default. As there can be multiple devices @@ -118,15 +126,20 @@ public class JBUI { * @see #sysScale(Graphics2D) * @see #sysScale(Component) */ - SYS, + SYS_SCALE, /** - * The pixel scale factor "combines" both the user and the system scale factors and defines the - * effective scale of the whole UI. + * An extra scale factor of a particular UI object, which doesn't affect any other UI object, as opposed + * to the user scale and the system scale factors. Doesn't depend on the HiDPI mode and is 1.0 by default. + */ + OBJ_SCALE, + /** + * The pixel scale factor "combines" all the other scale factors (user, system and object) and defines the + * effective scale of a particular UI object. * * For instance, on Mac Retina monitor (JRE-managed HiDPI) in the Presentation mode (which, say, - * doubles the UI scale) the pixel scale would equal 4.0. The value is the product of the user - * scale 2.0 and the system scale 2.0. In the IDE-managed HiDPI mode, the pixel scale always equals - * the user scale. + * doubles the UI scale) the pixel scale would equal 4.0 (provided the object scale is 1.0). The value + * is the product of the user scale 2.0 and the system scale 2.0. In the IDE-managed HiDPI mode, + * the pixel scale is the product of the user scale and the object scale. * * @see #pixScale() * @see #pixScale(GraphicsConfiguration) @@ -135,9 +148,47 @@ public class JBUI { * @see #pixScale(GraphicsConfiguration, float) * @see #pixScale(float) */ - PIX; + PIX_SCALE; - private final Key key = Key.create(name()); + public Scale of(double value) { + return new Scale(value, this); + } + } + + /** + * A scale factor of a particular type. + */ + public static class Scale { + private double value; + private final ScaleType type; + + public Scale(ScaleType type) { + this(1d, type); + } + + public Scale(double value, ScaleType type) { + this.value = value; + this.type = type; + } + + public double value() { + return value; + } + + public ScaleType type() { + return type; + } + + public boolean update(double value) { + boolean res = this.value != value; + this.value = value; + return res; + } + + @Override + public String toString() { + return "[" + type.name() + " " + value + "]"; + } } /** @@ -165,7 +216,7 @@ public class JBUI { } /** - * @see #addPropertyChangeListener(String, PropertyChangeListener) + * Removes property change listener */ public static void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { PCS.removePropertyChangeListener(propertyName, listener); @@ -201,17 +252,6 @@ public class JBUI { return getFontScale(size); } - /** - * Returns the system scale factor based on the JBUIScaleUpdatable. - * In the IDE-managed HiDPI mode defaults to {@link #sysScale()} - */ - public static float sysScale(JBUIScaleUpdatable updatable) { - if (UIUtil.isJreHiDPIEnabled() && updatable != null) { - return updatable.getJBUIScale(ScaleType.SYS); - } - return sysScale(); - } - /** * Returns the system scale factor, corresponding to the graphics configuration. * In the IDE-managed HiDPI mode defaults to {@link #sysScale()} @@ -259,6 +299,13 @@ public class JBUI { return sysScale(); } + public static double sysScale(@Nullable ScaleContext ctx) { + if (ctx != null) { + return ctx.getScale(SYS_SCALE); + } + return sysScale(); + } + /** * Returns the pixel scale factor, corresponding to the default monitor device. */ @@ -280,13 +327,6 @@ public class JBUI { return pixScale(gc) * f; } - /** - * Returns the pixel scale factor based on the JBUIScaleUpdatable. - */ - public static float pixScale(@NotNull JBUIScaleUpdatable updatableable) { - return UIUtil.isJreHiDPIEnabled() ? sysScale(updatableable) * updatableable.getJBUIScale(ScaleType.USR) : updatableable.getJBUIScale(ScaleType.USR); - } - /** * Returns the pixel scale factor, corresponding to the provided configuration. * In the IDE-managed HiDPI mode defaults to {@link #pixScale()} @@ -311,6 +351,14 @@ public class JBUI { return pixScale(comp != null ? comp.getGraphicsConfiguration() : null); } + public static double pixScale(@Nullable T ctx) { + if (ctx != null) { + double usrScale = ctx.getScale(USR_SCALE); + return UIUtil.isJreHiDPIEnabled() ? ctx.getScale(SYS_SCALE) * usrScale : usrScale; + } + return pixScale(); + } + private static void setUserScaleFactorProperty(float scale) { PCS.firePropertyChange(USER_SCALE_FACTOR_PROPERTY, userScaleFactor, userScaleFactor = scale); LOG.info("User scale factor: " + userScaleFactor); @@ -329,11 +377,7 @@ public class JBUI { return; } - if (scale < 1.25f) scale = 1.0f; - else if (scale < 1.5f) scale = 1.25f; - else if (scale < 1.75f) scale = 1.5f; - else if (scale < 2f) scale = 1.75f; - else scale = 2.0f; + scale = discreteScale(scale); if (SystemInfo.isLinux && scale == 1.25f) { //Default UI font size for Unity and Gnome is 15. Scaling factor 1.25f works badly on Linux @@ -345,6 +389,20 @@ public class JBUI { setUserScaleFactorProperty(scale); } + private static float discreteScale(float scale) { + int intPart = (int)Math.floor(scale); + float fractPart = scale - intPart; + + if (fractPart == 0) return scale; + + for (float f = 0; f < 1; f += DISCRETE_SCALE_RESOLUTION) { + if (fractPart < (f + DISCRETE_SCALE_RESOLUTION)) { + return intPart + f; + } + } + return intPart; // unreachable + } + /** * @return 'f' scaled by the user scale factor */ @@ -356,7 +414,7 @@ public class JBUI { * @return 'i' scaled by the user scale factor */ public static int scale(int i) { - return Math.round(userScaleFactor * i); + return round(userScaleFactor * i); } public static int scaleFontSize(float fontSize) { @@ -383,11 +441,7 @@ public class JBUI { public static JBDimension size(Dimension size) { if (size instanceof JBDimension) { - final JBDimension jbSize = (JBDimension)size; - if (jbSize.myJBUIScale == scale(1f)) { - return jbSize; - } - final JBDimension newSize = new JBDimension((int)(jbSize.width / jbSize.myJBUIScale), (int)(jbSize.height / jbSize.myJBUIScale)); + JBDimension newSize = ((JBDimension)size).newSize(); return size instanceof UIResource ? newSize.asUIResource() : newSize; } return new JBDimension(size.width, size.height); @@ -433,7 +487,7 @@ public class JBUI { } public static T scale(T icon) { - return (T)icon.withJBUIPreScaled(false); + return (T)icon.withIconPreScaled(false); } public static JBDimension emptySize() { @@ -453,7 +507,7 @@ public class JBUI { } /** - * Returns whether the {@link ScaleType#USR} scale factor assumes HiDPI-awareness. + * Returns whether the {@link ScaleType#USR_SCALE} scale factor assumes HiDPI-awareness. * An equivalent of {@code isHiDPI(scale(1f))} */ public static boolean isUsrHiDPI() { @@ -461,7 +515,7 @@ public class JBUI { } /** - * Returns whether the {@link ScaleType#PIX} scale factor assumes HiDPI-awareness in the provided graphics config. + * Returns whether the {@link ScaleType#PIX_SCALE} scale factor assumes HiDPI-awareness in the provided graphics config. * An equivalent of {@code isHiDPI(pixScale(gc))} */ public static boolean isPixHiDPI(@Nullable GraphicsConfiguration gc) { @@ -469,7 +523,7 @@ public class JBUI { } /** - * Returns whether the {@link ScaleType#PIX} scale factor assumes HiDPI-awareness in the provided graphics. + * Returns whether the {@link ScaleType#PIX_SCALE} scale factor assumes HiDPI-awareness in the provided graphics. * An equivalent of {@code isHiDPI(pixScale(g))} */ public static boolean isPixHiDPI(@Nullable Graphics2D g) { @@ -477,7 +531,7 @@ public class JBUI { } /** - * Returns whether the {@link ScaleType#PIX} scale factor assumes HiDPI-awareness in the provided component's device. + * Returns whether the {@link ScaleType#PIX_SCALE} scale factor assumes HiDPI-awareness in the provided component's device. * An equivalent of {@code isHiDPI(pixScale(comp))} */ public static boolean isPixHiDPI(@Nullable Component comp) { @@ -487,7 +541,7 @@ public class JBUI { /** * Returns whether the provided scale assumes HiDPI-awareness. */ - public static boolean isHiDPI(float scale) { + public static boolean isHiDPI(double scale) { return scale > 1f; } @@ -579,68 +633,446 @@ public class JBUI { } /** - * An Icon dynamically sticking to JBUI.scale to meet HiDPI. + * A wrapper over a user scale supplier, representing a state of a UI element + * in which its initial size is either pre-scaled (according to {@link #currentScale()}) + * or not (given in a standard resolution, e.g. 16x16 for an icon). + */ + public static abstract class Scaler { + protected double initialScale; + + public Scaler() { + this(false); + } + + public Scaler(boolean preScaled) { + initialScale = preScaled ? currentScale() : 1d; + } + + private double alignedScale() { + return currentScale() / initialScale; + } + + protected boolean isPreScaled() { + return initialScale != 1d; + } + + protected void setPreScaled(boolean preScaled) { + initialScale = preScaled ? currentScale() : 1d; + } + + /** + * @param value the value (e.g. a size of the associated UI object) to scale + * @return the scaled result, taking into account the pre-scaled state and {@link #currentScale()} + */ + public double scaleVal(double value) { + return value * alignedScale(); + } + + /** + * Supplies the Scaler with the current user scale. This can be the current global user scale or + * the context scale ({@link BaseScaleContext#usrScale}) or something else. + */ + protected abstract double currentScale(); + } + + /** + * Represents a snapshot of the scale factors (see {@link ScaleType}), except the system scale. + * The context can be associated with a UI object (see {@link ScaleContextAware}) to define its HiDPI behaviour. + * Unlike {@link ScaleContext}, BaseScaleContext is system scale independent and is thus used for vector-based painting. + * + * @see ScaleContextAware + * @see ScaleContext + * @author tav + */ + public static class BaseScaleContext { + protected Scale usrScale = USR_SCALE.of(scale(1f)); + protected Scale objScale = OBJ_SCALE.of(1d); + protected Scale pixScale = PIX_SCALE.of(usrScale.value); + + private List listeners; + + private BaseScaleContext() { + } + + /** + * Creates a context with the provided scale factors (system scale is ignored) + */ + public static BaseScaleContext create(@NotNull Scale... scales) { + BaseScaleContext ctx = create(); + for (Scale s : scales) ctx.update(s); + return ctx; + } + + /** + * Creates a default context with the current user scale + */ + public static BaseScaleContext create() { + return new BaseScaleContext(); + } + + protected double derivePixScale() { + return usrScale.value * objScale.value; + } + + /** + * @return the context scale factor of the provided type (1d for system scale) + */ + public double getScale(ScaleType type) { + switch (type) { + case USR_SCALE: return usrScale.value; + case SYS_SCALE: return 1d; + case OBJ_SCALE: return objScale.value; + case PIX_SCALE: return pixScale.value; + } + return 1f; // unreachable + } + + protected boolean onUpdated(boolean updated) { + if (updated) { + pixScale.update(derivePixScale()); + notifyUpdateListeners(); + } + return updated; + } + + /** + * Updates the user scale with the current global user scale if necessary. + * + * @return whether any of the scale factors has been updated + */ + public boolean update() { + return onUpdated(usrScale.update(scale(1f))); + } + + /** + * Updates the provided scale if necessary (system scale is ignored) + * + * @param scale the new scale + * @return whether the scale factor has been updated + */ + public boolean update(@NotNull Scale scale) { + boolean updated = false; + switch (scale.type) { + case USR_SCALE: updated = usrScale.update(scale.value); break; + case SYS_SCALE: break; + case OBJ_SCALE: updated = objScale.update(scale.value); break; + case PIX_SCALE: break; + } + return onUpdated(updated); + } + + /** + * Updates the context with the state of the provided one. + * + * @param ctx the new context + * @return whether any of the scale factors has been updated + */ + public boolean update(@Nullable BaseScaleContext ctx) { + if (ctx == null) return update(); + return onUpdated(updateAll(ctx)); + } + + protected boolean updateAll(@NotNull BaseScaleContext ctx) { + boolean updated = usrScale.update(ctx.usrScale.value); + return objScale.update(ctx.objScale.value) || updated; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (!(obj instanceof BaseScaleContext)) return false; + + BaseScaleContext that = (BaseScaleContext)obj; + return that.usrScale.value == usrScale.value && + that.objScale.value == objScale.value; + } + + /** + * Clears the links. + */ + public void dispose() { + listeners = null; + } + + /** + * A context update listener. Used to listen to possible external context updates. + */ + public interface UpdateListener { + void contextUpdated(); + } + + public void addUpdateListener(UpdateListener l) { + if (listeners == null) listeners = new ArrayList(1); + listeners.add(l); + } + + public void removeUpdateListener(UpdateListener l) { + if (listeners != null) listeners.remove(l); + } + + protected void notifyUpdateListeners() { + if (listeners == null) return; + for (UpdateListener l : listeners) { + l.contextUpdated(); + } + } + } + + /** + * Extends {@link BaseScaleContext} with the system scale, and is thus used for raster-based painting. + * The context is created via a context provider. If the provider is {@link Component}, the context's + * system scale can be updated via a call to {@link #update()}, reflecting the current component's + * system scale (which may change as the component moves b/w devices). + * + * @see ScaleContextAware + * @author tav + */ + public static class ScaleContext extends BaseScaleContext { + protected Scale sysScale = SYS_SCALE.of(sysScale()); + + private @Nullable WeakReference compRef; + + private ScaleContext() { + pixScale.update(derivePixScale()); + } + + private ScaleContext(Scale scale) { + switch (scale.type) { + case USR_SCALE: usrScale.update(scale.value); break; + case SYS_SCALE: sysScale.update(scale.value); break; + case OBJ_SCALE: objScale.update(scale.value); break; + case PIX_SCALE: break; + } + pixScale.update(derivePixScale()); + } + + /** + * Creates a context based on the comp's system scale and sticks to it via the {@link #update()} method. + */ + public static ScaleContext create(@NotNull Component comp) { + final ScaleContext ctx = new ScaleContext(SYS_SCALE.of(sysScale(comp))); + ctx.compRef = new WeakReference(comp); + return ctx; + } + + /** + * Creates a context based on the gc's system scale + */ + public static ScaleContext create(GraphicsConfiguration gc) { + return new ScaleContext(SYS_SCALE.of(sysScale(gc))); + } + + /** + * Creates a context based on the g's system scale + */ + public static ScaleContext create(Graphics2D g) { + return new ScaleContext(SYS_SCALE.of(sysScale(g))); + } + + /** + * Creates a context with the provided scale + */ + public static ScaleContext create(@NotNull Scale scale) { + return new ScaleContext(scale); + } + + /** + * Creates a context with the provided scale factors + */ + public static ScaleContext create(@NotNull Scale... scales) { + ScaleContext ctx = create(); + for (Scale s : scales) ctx.update(s); + return ctx; + } + + /** + * Creates a default context with the default screen scale and the current user scale + */ + public static ScaleContext create() { + return new ScaleContext(); + } + + @Override + protected double derivePixScale() { + return UIUtil.isJreHiDPIEnabled() ? sysScale.value * super.derivePixScale() : super.derivePixScale(); + } + + /** + * {@inheritDoc} + */ + @Override + public double getScale(ScaleType type) { + if (type == SYS_SCALE) return sysScale.value; + return super.getScale(type); + } + + /** + * {@inheritDoc} + * Also updates the system scale (if the context was created from Component) if necessary. + */ + @Override + public boolean update() { + boolean updated = usrScale.update(scale(1f)); + if (compRef != null) { + Component comp = compRef.get(); + if (comp != null) updated = sysScale.update(sysScale(comp)) || updated; + } + return onUpdated(updated); + } + + /** + * {@inheritDoc} + * Also includes the system scale. + */ + @Override + public boolean update(@NotNull Scale scale) { + if (scale.type == SYS_SCALE) return onUpdated(sysScale.update(scale.value)); + return super.update(scale); + } + + /** + * {@inheritDoc} + */ + public boolean update(@Nullable ScaleContext ctx) { + if (ctx == null) return update(); + + if (compRef != null) compRef.clear(); + compRef = ctx.compRef; + + return onUpdated(updateAll(ctx)); + } + + protected boolean updateAll(@NotNull ScaleContext ctx) { + boolean updated = super.updateAll(ctx); + return sysScale.update(ctx.sysScale.value) || updated; + } + + @Override + public boolean equals(Object obj) { + if (super.equals(obj) && obj instanceof ScaleContext) { + ScaleContext that = (ScaleContext)obj; + return that.sysScale.value == sysScale.value; + } + return false; + } + + @Override + public void dispose() { + super.dispose(); + if (compRef != null) { + compRef.clear(); + } + } + } + + /** + * Provides ScaleContext awareness of a UI object. + * + * @see ScaleContextSupport + * @author tav + */ + public interface ScaleContextAware { + /** + * @return the scale context + */ + @NotNull T getScaleContext(); + + /** + * Updates the current context with the state of the provided context. + * If {@code ctx} is null, then updates the current context via {@link ScaleContext#update()} + * and returns the result. + * + * @param ctx the new scale context + * @return whether any of the scale factors has been updated + */ + boolean updateScaleContext(@Nullable T ctx); + + /** + * @return the scale of the provided type from the context + */ + double getScale(ScaleType type); + + /** + * Updates the provided scale in the context + * + * @return whether the provided scale has been changed + */ + boolean updateScale(Scale scale); + } + + public static class ScaleContextSupport implements ScaleContextAware { + private final T myScaleContext; + + private ScaleContextSupport() { + myScaleContext = null; + assert false; + } + + public ScaleContextSupport(T ctx) { + myScaleContext = ctx; + } + + @Override + public @NotNull T getScaleContext() { + return myScaleContext; + } + + @Override + public boolean updateScaleContext(@Nullable T ctx) { + return myScaleContext.update(ctx); + } + + @Override + public double getScale(ScaleType type) { + return getScaleContext().getScale(type); + } + + @Override + public boolean updateScale(Scale scale) { + return getScaleContext().update(scale); + } + } + + /** + * A {@link BaseScaleContext} aware Icon, assuming vector-based painting, system scale independent. * * @author tav */ - public abstract static class JBIcon implements Icon { - private float myInitialJBUIScale = currentJBUIScale(); + public abstract static class JBIcon extends ScaleContextSupport implements Icon { + private final Scaler myScaler = new Scaler(false) { + @Override + protected double currentScale() { + return getScale(USR_SCALE); + } + }; - protected JBIcon() {} + protected JBIcon() { + super(BaseScaleContext.create()); + } protected JBIcon(JBIcon icon) { - myInitialJBUIScale = icon.myInitialJBUIScale; + this(); + updateScaleContext(icon.getScaleContext()); } - static float currentJBUIScale() { - // We don't JBUI-scale images in JRE-managed HiDPI mode, see comments in ImageLoader.loadFromUrl(..) - // So, make icons JBUI-scale conformant. - return UIUtil.isJreHiDPIEnabled() ? 1f : scale(1f); + protected boolean isIconPreScaled() { + return myScaler.isPreScaled(); } - /** - * @return the scale factor aligning the icon size metrics to conform to up-to-date JBUI.scale - */ - private float getAligningScale() { - return currentJBUIScale() / myInitialJBUIScale; + protected void setIconPreScaled(boolean preScaled) { + myScaler.setPreScaled(preScaled); } - /** - * @return whether the icon size metrics are pre-scaled or not - */ - protected boolean isJBUIPreScaled() { - return myInitialJBUIScale != 1f; - } - - /** - * Sets the icon size metrics to {@code preScaled} - */ - protected void setJBUIPreScaled(boolean preScaled) { - myInitialJBUIScale = preScaled ? currentJBUIScale() : 1f; - } - - /** - * Sets the icon size metrics to {@code preScaled} - * - * @return the icon (this or new instance) with size metrics set to {@code preScaled} - */ - public JBIcon withJBUIPreScaled(boolean preScaled) { - setJBUIPreScaled(preScaled); + public JBIcon withIconPreScaled(boolean preScaled) { + setIconPreScaled(preScaled); return this; } /** - * Scales the value to conform to JBUI.scale + * See {@link Scaler#scaleVal(double)} */ - public int scaleVal(int value) { - return (int)scaleVal((float)value); - } - - /** - * Scales the value to conform to JBUI.scale - */ - public float scaleVal(float value) { - return value * getAligningScale(); + protected double scaleVal(double value) { + return myScaler.scaleVal(value); } @Override @@ -650,93 +1082,63 @@ public class JBUI { } /** - * An Icon supporting both JBUI.scale & instance scale factors. + * A {@link JBIcon} implementing {@link ScalableIcon} * * @author tav */ public abstract static class ScalableJBIcon extends JBIcon implements ScalableIcon { - private float myScale = 1f; - protected ScalableJBIcon() {} protected ScalableJBIcon(ScalableJBIcon icon) { super(icon); - myScale = icon.myScale; - } - - public enum Scale { - JBUI, // JBIcon's JBUI scale - INSTANCE, // ScalableIcon's instance scale - EFFECTIVE // effective scale } @Override public float getScale() { - return myScale; - } - - protected void setScale(float scale) { - myScale = scale; + return (float)getScale(OBJ_SCALE); // todo: float -> double } @Override - public int scaleVal(int value) { - return scaleVal(value, Scale.EFFECTIVE); + public Icon scale(float scale) { + updateScale(OBJ_SCALE.of(scale)); + return this; } + /** + * An equivalent of scaleVal(value, PIX_SCALE) + */ @Override - public float scaleVal(float value) { - return scaleVal(value, Scale.EFFECTIVE); + protected double scaleVal(double value) { + return scaleVal(value, PIX_SCALE); } - public int scaleVal(int value, Scale type) { - return (int)scaleVal((float)value, type); - } - - public float scaleVal(float value, Scale type) { + /** + * Updates the context and scales the provided value according to the provided type + */ + protected double scaleVal(double value, ScaleType type) { switch (type) { - case JBUI: - return super.scaleVal(value); - case INSTANCE: - return value * myScale; - case EFFECTIVE: - default: - return super.scaleVal(value * myScale); + case USR_SCALE: return super.scaleVal(value); + case SYS_SCALE: return value * getScale(SYS_SCALE); + case OBJ_SCALE: return value * getScale(OBJ_SCALE); + case PIX_SCALE: return super.scaleVal(value * getScale(OBJ_SCALE)); } - } - - /** - * 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; + return value; // unreachable } } /** - * A ScalableJBIcon providing an immutable caching implementation of the {@link #scale(float)} method. + * A {@link ScalableJBIcon} providing an immutable caching implementation of the {@link ScalableIcon#scale(float)} method. * * @author tav * @author Aleksey Pivovarov */ public abstract static class CachingScalableJBIcon extends ScalableJBIcon { - private CachingScalableJBIcon myScaledIconCache; + private CachingScalableJBIcon myScaledIconCache = null; protected CachingScalableJBIcon() {} protected CachingScalableJBIcon(CachingScalableJBIcon icon) { super(icon); - myScaledIconCache = null; } /** @@ -748,7 +1150,7 @@ public class JBUI { if (myScaledIconCache == null || myScaledIconCache.getScale() != scale) { myScaledIconCache = copy(); - myScaledIconCache.setScale(scale); + myScaledIconCache.updateScale(OBJ_SCALE.of(scale)); } return myScaledIconCache; } @@ -761,221 +1163,13 @@ public class JBUI { } /** - * An interface to update JBUI scale factors. - */ - public interface JBUIScaleUpdatable { - /** - * Checks if tracked user scale should be updated and updates it. - * - * @return true if tracked user scale was updated - */ - boolean updateJBUIScale(); - - /** - * Updates all the scale factors based on the provided graphics. - * - * @param g the graphics, if null defaults to {@link #updateJBUIScale()} - * @return true if any of the tracked scale factors was updated - */ - boolean updateJBUIScale(@Nullable Graphics2D g); - - /** - * Updates all the scale factors based on the provided graphics config. - * - * @param gc the graphics config, if null defaults to {@link #updateJBUIScale()} - * @return true if any of the tracked scale factors was updated - */ - boolean updateJBUIScale(@Nullable GraphicsConfiguration gc); - - /** - * @return true if tracked user scale should be updated - */ - boolean needUpdateJBUIScale(); - - /** - * @param g the graphics, if null defaults to {@link #needUpdateJBUIScale()} - * @return true if any of the tracked scale factors should be updated - */ - boolean needUpdateJBUIScale(@Nullable Graphics2D g); - - /** - * @param gc the graphics config, if null defaults to {@link #needUpdateJBUIScale()} - * @return true if any of the tracked scale factors should be updated - */ - boolean needUpdateJBUIScale(@Nullable GraphicsConfiguration gc); - - /** - * @param type the type of the scale - * @return the tracked scale factor value of the type - */ - float getJBUIScale(ScaleType type); - } - - /** - * A helper class to update JBUI scale factors. - */ - private static class JBUIScaleUpdater implements JBUIScaleUpdatable { - // ScaleType.USR - tracked - // ScaleType.SYS - tracked - // ScaleType.PIX - derived - KeyFMap myTrackedJBUIScale = KeyFMap.EMPTY_MAP; - - { - put(ScaleType.USR.key, JBIcon.currentJBUIScale()); - put(ScaleType.SYS.key, sysScale()); - } - - private void put(Key key, Float value) { - myTrackedJBUIScale = myTrackedJBUIScale.plus(key, value); - } - - @Override - public boolean updateJBUIScale() { - return updateJBUIScale(JBIcon.currentJBUIScale(), ScaleType.USR); - } - - private boolean updateJBUIScale(float scale, ScaleType type) { - if (needUpdateJBUIScale(scale, type)) { - put(type.key, scale); - return true; - } - return false; - } - - @Override - public boolean updateJBUIScale(@Nullable Graphics2D g) { - boolean res = updateJBUIScale(); - if (g != null) res = res || updateJBUIScale(sysScale(g), ScaleType.SYS); - return res; - } - - @Override - public boolean updateJBUIScale(@Nullable GraphicsConfiguration gc) { - boolean res = updateJBUIScale(); - if (gc != null) res = res || updateJBUIScale(sysScale(gc), ScaleType.SYS); - return res; - } - - @Override - public boolean needUpdateJBUIScale() { - return needUpdateJBUIScale(JBIcon.currentJBUIScale(), ScaleType.USR); - } - - private boolean needUpdateJBUIScale(float scale, ScaleType type) { - return getJBUIScale(type) != scale; - } - - @Override - public boolean needUpdateJBUIScale(@Nullable Graphics2D g) { - return needUpdateJBUIScale() || g != null && needUpdateJBUIScale(sysScale(g), ScaleType.SYS); - } - - @Override - public boolean needUpdateJBUIScale(@Nullable GraphicsConfiguration gc) { - return needUpdateJBUIScale() || gc != null && needUpdateJBUIScale(sysScale(gc), ScaleType.SYS); - } - - @Override - public float getJBUIScale(ScaleType type) { - return type == ScaleType.PIX ? - pixScale(this) : // derive - myTrackedJBUIScale.get(type.key); - } - } - - /** - * A JBIcon lazily updating JBUI scale factors change. + * A {@link ScaleContext} aware Icon, assuming raster-based painting, system scale dependant. * * @author tav */ - public abstract static class UpdatingJBIcon extends JBIcon implements JBUIScaleUpdatable { - private final JBUIScaleUpdater myJBUIScaleDelegate = new JBUIScaleUpdater(); - - @Override - public boolean updateJBUIScale() { - return myJBUIScaleDelegate.updateJBUIScale(); - } - - @Override - public boolean updateJBUIScale(@Nullable Graphics2D g) { - return myJBUIScaleDelegate.updateJBUIScale(g); - } - - @Override - public boolean updateJBUIScale(@Nullable GraphicsConfiguration gc) { - return myJBUIScaleDelegate.updateJBUIScale(gc); - } - - @Override - public boolean needUpdateJBUIScale() { - return myJBUIScaleDelegate.needUpdateJBUIScale(); - } - - @Override - public boolean needUpdateJBUIScale(@Nullable Graphics2D g) { - return myJBUIScaleDelegate.needUpdateJBUIScale(g); - } - - @Override - public boolean needUpdateJBUIScale(@Nullable GraphicsConfiguration gc) { - return myJBUIScaleDelegate.needUpdateJBUIScale(gc); - } - - @Override - public float getJBUIScale(ScaleType type) { - return myJBUIScaleDelegate.getJBUIScale(type); - } - } - - /** - * A ScalableJBIcon lazily updating JBUI scale factors change. - * - * @author tav - */ - public abstract static class UpdatingScalableJBIcon - extends CachingScalableJBIcon implements JBUIScaleUpdatable { - - private final JBUIScaleUpdater myJBUIScaleDelegate = new JBUIScaleUpdater(); - - protected UpdatingScalableJBIcon() {} - - protected UpdatingScalableJBIcon(UpdatingScalableJBIcon icon) { - super(icon); - } - - @Override - public boolean updateJBUIScale() { - return myJBUIScaleDelegate.updateJBUIScale(); - } - - @Override - public boolean updateJBUIScale(@Nullable Graphics2D g) { - return myJBUIScaleDelegate.updateJBUIScale(g); - } - - @Override - public boolean updateJBUIScale(@Nullable GraphicsConfiguration gc) { - return myJBUIScaleDelegate.updateJBUIScale(gc); - } - - @Override - public boolean needUpdateJBUIScale() { - return myJBUIScaleDelegate.needUpdateJBUIScale(); - } - - @Override - public boolean needUpdateJBUIScale(@Nullable Graphics2D g) { - return myJBUIScaleDelegate.needUpdateJBUIScale(g); - } - - @Override - public boolean needUpdateJBUIScale(@Nullable GraphicsConfiguration gc) { - return myJBUIScaleDelegate.needUpdateJBUIScale(gc); - } - - @Override - public float getJBUIScale(ScaleType type) { - return myJBUIScaleDelegate.getJBUIScale(type); + public abstract static class RasterJBIcon extends ScaleContextSupport implements Icon { + public RasterJBIcon() { + super(ScaleContext.create()); } } } diff --git a/platform/util/src/com/intellij/util/ui/UIUtil.java b/platform/util/src/com/intellij/util/ui/UIUtil.java index 6483e3aa31b9..67757481f2e9 100644 --- a/platform/util/src/com/intellij/util/ui/UIUtil.java +++ b/platform/util/src/com/intellij/util/ui/UIUtil.java @@ -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; diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/StructureFilterPopupComponent.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/StructureFilterPopupComponent.java index a0e8cb48889b..bb3e7f8e9680 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/StructureFilterPopupComponent.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/StructureFilterPopupComponent.java @@ -331,9 +331,9 @@ class StructureFilterPopupComponent extends FilterPopupComponent