diff --git a/src/java.desktop/share/classes/com/sun/java/swing/SwingUtilities3.java b/src/java.desktop/share/classes/com/sun/java/swing/SwingUtilities3.java
index 199026aae3b..8040120695d 100644
--- a/src/java.desktop/share/classes/com/sun/java/swing/SwingUtilities3.java
+++ b/src/java.desktop/share/classes/com/sun/java/swing/SwingUtilities3.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2002, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -25,19 +25,26 @@
package com.sun.java.swing;
-import sun.awt.AppContext;
-import sun.awt.SunToolkit;
-
-import java.util.Collections;
-import java.util.Map;
-import java.util.WeakHashMap;
import java.applet.Applet;
import java.awt.Component;
import java.awt.Container;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Stroke;
import java.awt.Window;
+import java.awt.geom.AffineTransform;
+import java.util.Collections;
+import java.util.Map;
+import java.util.WeakHashMap;
+
import javax.swing.JComponent;
import javax.swing.RepaintManager;
+import sun.awt.AppContext;
+import sun.awt.SunToolkit;
+
+import static sun.java2d.pipe.Region.clipRound;
+
/**
* A collection of utility methods for Swing.
*
@@ -135,4 +142,114 @@ public class SwingUtilities3 {
}
return delegate;
}
+
+ /**
+ * A task which paints an unscaled border after {@code Graphics}
+ * transforms are removed. It's used with the
+ * {@link #paintBorder(Component, Graphics, int, int, int, int, UnscaledBorderPainter)
+ * SwingUtilities3.paintBorder} which manages changing the transforms and calculating
+ * the coordinates and size of the border.
+ */
+ @FunctionalInterface
+ public interface UnscaledBorderPainter {
+ /**
+ * Paints the border for the specified component after the
+ * {@code Graphics} transforms are removed.
+ *
+ *
+ * The x and y of the painted border are zero.
+ *
+ * @param c the component for which this border is being painted
+ * @param g the paint graphics
+ * @param w the width of the painted border, in physical pixels
+ * @param h the height of the painted border, in physical pixels
+ * @param scaleFactor the scale that was in the {@code Graphics}
+ *
+ * @see #paintBorder(Component, Graphics, int, int, int, int, UnscaledBorderPainter)
+ * SwingUtilities3.paintBorder
+ * @see javax.swing.border.Border#paintBorder(Component, Graphics, int, int, int, int)
+ * Border.paintBorder
+ */
+ void paintUnscaledBorder(Component c, Graphics g,
+ int w, int h,
+ double scaleFactor);
+ }
+
+ /**
+ * Paints the border for a component ensuring its sides have consistent
+ * thickness at different scales.
+ *
+ * It performs the following steps:
+ *
+ * - Reset the scale transform on the {@code Graphics},
+ * - Call {@code painter} to paint the border,
+ * - Restores the transform.
+ *
+ *
+ * @param c the component for which this border is being painted
+ * @param g the paint graphics
+ * @param x the x position of the painted border
+ * @param y the y position of the painted border
+ * @param w the width of the painted border
+ * @param h the height of the painted border
+ * @param painter the painter object which paints the border after
+ * the transform on the {@code Graphics} is reset
+ */
+ public static void paintBorder(Component c, Graphics g,
+ int x, int y,
+ int w, int h,
+ UnscaledBorderPainter painter) {
+
+ // Step 1: Reset Transform
+ AffineTransform at = null;
+ Stroke oldStroke = null;
+ boolean resetTransform = false;
+ double scaleFactor = 1;
+
+ int xtranslation = x;
+ int ytranslation = y;
+ int width = w;
+ int height = h;
+
+ if (g instanceof Graphics2D) {
+ Graphics2D g2d = (Graphics2D) g;
+ at = g2d.getTransform();
+ oldStroke = g2d.getStroke();
+ scaleFactor = Math.min(at.getScaleX(), at.getScaleY());
+
+ // if m01 or m10 is non-zero, then there is a rotation or shear,
+ // or if scale=1, skip resetting the transform in these cases.
+ resetTransform = ((at.getShearX() == 0) && (at.getShearY() == 0))
+ && ((at.getScaleX() > 1) || (at.getScaleY() > 1));
+
+ if (resetTransform) {
+ /* Deactivate the HiDPI scaling transform,
+ * so we can do paint operations in the device
+ * pixel coordinate system instead of the logical coordinate system.
+ */
+ g2d.setTransform(new AffineTransform());
+ double xx = at.getScaleX() * x + at.getTranslateX();
+ double yy = at.getScaleY() * y + at.getTranslateY();
+ xtranslation = clipRound(xx);
+ ytranslation = clipRound(yy);
+ width = clipRound(at.getScaleX() * w + xx) - xtranslation;
+ height = clipRound(at.getScaleY() * h + yy) - ytranslation;
+ }
+ }
+
+ g.translate(xtranslation, ytranslation);
+
+ // Step 2: Call respective paintBorder with transformed values
+ painter.paintUnscaledBorder(c, g, width, height, scaleFactor);
+
+ // Step 3: Restore previous stroke & transform
+ g.translate(-xtranslation, -ytranslation);
+ if (g instanceof Graphics2D) {
+ Graphics2D g2d = (Graphics2D) g;
+ g2d.setStroke(oldStroke);
+ if (resetTransform) {
+ g2d.setTransform(at);
+ }
+ }
+ }
}
diff --git a/src/java.desktop/share/classes/javax/swing/border/EtchedBorder.java b/src/java.desktop/share/classes/javax/swing/border/EtchedBorder.java
index 95021b4b4bc..cd2068b0afa 100644
--- a/src/java.desktop/share/classes/javax/swing/border/EtchedBorder.java
+++ b/src/java.desktop/share/classes/javax/swing/border/EtchedBorder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -30,10 +30,10 @@ import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Color;
import java.awt.Component;
-import java.awt.Stroke;
-import java.awt.geom.AffineTransform;
import java.beans.ConstructorProperties;
+import com.sun.java.swing.SwingUtilities3;
+
/**
* A class which implements a simple etched border which can
* either be etched-in or etched-out. If no highlight/shadow
@@ -150,59 +150,26 @@ public class EtchedBorder extends AbstractBorder
* @param height the height of the painted border
*/
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
- // We remove any initial transforms to prevent rounding errors
- // when drawing in non-integer scales
- AffineTransform at = null;
- Stroke oldStk = null;
- int stkWidth = 1;
- boolean resetTransform = false;
+ SwingUtilities3.paintBorder(c, g,
+ x, y,
+ width, height,
+ this::paintUnscaledBorder);
+ }
+
+ private void paintUnscaledBorder(Component c, Graphics g,
+ int w, int h,
+ double scaleFactor) {
+ int stkWidth = (int) Math.floor(scaleFactor);
if (g instanceof Graphics2D) {
- Graphics2D g2d = (Graphics2D) g;
- at = g2d.getTransform();
- oldStk = g2d.getStroke();
- // if m01 or m10 is non-zero, then there is a rotation or shear
- // skip resetting the transform
- resetTransform = (at.getShearX() == 0) && (at.getShearY() == 0);
- if (resetTransform) {
- g2d.setTransform(new AffineTransform());
- stkWidth = (int) Math.floor(Math.min(at.getScaleX(), at.getScaleY()));
- g2d.setStroke(new BasicStroke((float) stkWidth));
- }
+ ((Graphics2D) g).setStroke(new BasicStroke((float) stkWidth));
}
- int w;
- int h;
- int xtranslation;
- int ytranslation;
- if (resetTransform) {
- w = (int) Math.floor(at.getScaleX() * width - 1);
- h = (int) Math.floor(at.getScaleY() * height - 1);
- xtranslation = (int) Math.ceil(at.getScaleX()*x+at.getTranslateX());
- ytranslation = (int) Math.ceil(at.getScaleY()*y+at.getTranslateY());
- } else {
- w = width;
- h = height;
- xtranslation = x;
- ytranslation = y;
- }
-
- g.translate(xtranslation, ytranslation);
-
paintBorderShadow(g, (etchType == LOWERED) ? getHighlightColor(c)
: getShadowColor(c),
w, h, stkWidth);
paintBorderHighlight(g, (etchType == LOWERED) ? getShadowColor(c)
: getHighlightColor(c),
w, h, stkWidth);
-
- g.translate(-xtranslation, -ytranslation);
-
- // Set the transform we removed earlier
- if (resetTransform) {
- Graphics2D g2d = (Graphics2D) g;
- g2d.setTransform(at);
- g2d.setStroke(oldStk);
- }
}
/**
diff --git a/src/java.desktop/share/classes/javax/swing/border/LineBorder.java b/src/java.desktop/share/classes/javax/swing/border/LineBorder.java
index 39183afb263..9116731eaf0 100644
--- a/src/java.desktop/share/classes/javax/swing/border/LineBorder.java
+++ b/src/java.desktop/share/classes/javax/swing/border/LineBorder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -34,9 +34,8 @@ import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.beans.ConstructorProperties;
-import java.awt.geom.AffineTransform;
-import static sun.java2d.pipe.Region.clipRound;
+import com.sun.java.swing.SwingUtilities3;
/**
* A class which implements a line border of arbitrary thickness
@@ -144,73 +143,41 @@ public class LineBorder extends AbstractBorder
* @param height the height of the painted border
*/
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+ SwingUtilities3.paintBorder(c, g,
+ x, y,
+ width, height,
+ this::paintUnscaledBorder);
+ }
+
+ private void paintUnscaledBorder(Component c, Graphics g,
+ int w, int h,
+ double scaleFactor) {
if ((this.thickness > 0) && (g instanceof Graphics2D)) {
Graphics2D g2d = (Graphics2D) g;
- AffineTransform at = g2d.getTransform();
-
- // if m01 or m10 is non-zero, then there is a rotation or shear
- // or if no Scaling enabled,
- // skip resetting the transform
- boolean resetTransform = ((at.getShearX() == 0) && (at.getShearY() == 0)) &&
- ((at.getScaleX() > 1) || (at.getScaleY() > 1));
-
- int xtranslation;
- int ytranslation;
- int w;
- int h;
- int offs;
-
- if (resetTransform) {
- /* Deactivate the HiDPI scaling transform,
- * so we can do paint operations in the device
- * pixel coordinate system instead of the logical coordinate system.
- */
- g2d.setTransform(new AffineTransform());
- double xx = at.getScaleX() * x + at.getTranslateX();
- double yy = at.getScaleY() * y + at.getTranslateY();
- xtranslation = clipRound(xx);
- ytranslation = clipRound(yy);
- w = clipRound(at.getScaleX() * width + xx) - xtranslation;
- h = clipRound(at.getScaleY() * height + yy) - ytranslation;
- offs = this.thickness * (int) at.getScaleX();
- } else {
- w = width;
- h = height;
- xtranslation = x;
- ytranslation = y;
- offs = this.thickness;
- }
-
- g2d.translate(xtranslation, ytranslation);
-
Color oldColor = g2d.getColor();
g2d.setColor(this.lineColor);
Shape outer;
Shape inner;
+ int offs = this.thickness * (int) scaleFactor;
int size = offs + offs;
if (this.roundedCorners) {
float arc = .2f * offs;
outer = new RoundRectangle2D.Float(0, 0, w, h, offs, offs);
inner = new RoundRectangle2D.Float(offs, offs, w - size, h - size, arc, arc);
- }
- else {
+ } else {
outer = new Rectangle2D.Float(0, 0, w, h);
inner = new Rectangle2D.Float(offs, offs, w - size, h - size);
}
+
Path2D path = new Path2D.Float(Path2D.WIND_EVEN_ODD);
path.append(outer, false);
path.append(inner, false);
g2d.fill(path);
+
g2d.setColor(oldColor);
-
- g2d.translate(-xtranslation, -ytranslation);
-
- if (resetTransform) {
- g2d.setTransform(at);
- }
}
}
diff --git a/src/java.desktop/share/classes/javax/swing/plaf/metal/MetalBorders.java b/src/java.desktop/share/classes/javax/swing/plaf/metal/MetalBorders.java
index 38b4a692395..c7ebc992166 100644
--- a/src/java.desktop/share/classes/javax/swing/plaf/metal/MetalBorders.java
+++ b/src/java.desktop/share/classes/javax/swing/plaf/metal/MetalBorders.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1998, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -33,9 +33,7 @@ import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
-import java.awt.Stroke;
import java.awt.Window;
-import java.awt.geom.AffineTransform;
import javax.swing.AbstractButton;
import javax.swing.ButtonModel;
@@ -62,6 +60,7 @@ import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicBorders;
import javax.swing.text.JTextComponent;
+import com.sun.java.swing.SwingUtilities3;
import sun.swing.StringUIClientPropertyKey;
import sun.swing.SwingUtilities2;
@@ -250,6 +249,14 @@ public class MetalBorders {
public void paintBorder(Component c, Graphics g, int x, int y,
int w, int h) {
+ SwingUtilities3.paintBorder(c, g,
+ x, y, w, h,
+ this::paintUnscaledBorder);
+ }
+
+ private void paintUnscaledBorder(Component c, Graphics g,
+ int width, int height,
+ double scaleFactor) {
Color background;
Color highlight;
Color shadow;
@@ -264,48 +271,6 @@ public class MetalBorders {
shadow = MetalLookAndFeel.getControlInfo();
}
- AffineTransform at = null;
- Stroke oldStk = null;
- boolean resetTransform = false;
- int stkWidth = 1;
- double scaleFactor = 1;
-
- if (g instanceof Graphics2D g2d) {
- at = g2d.getTransform();
- scaleFactor = at.getScaleX();
- oldStk = g2d.getStroke();
-
- // if m01 or m10 is non-zero, then there is a rotation or shear
- // skip resetting the transform
- resetTransform = ((at.getShearX() == 0) && (at.getShearY() == 0));
-
- if (resetTransform) {
- g2d.setTransform(new AffineTransform());
- stkWidth = clipRound(Math.min(at.getScaleX(), at.getScaleY()));
- g2d.setStroke(new BasicStroke((float) stkWidth));
- }
- }
-
- int xtranslation;
- int ytranslation;
- int width;
- int height;
-
- if (resetTransform) {
- double xx = at.getScaleX() * x + at.getTranslateX();
- double yy = at.getScaleY() * y + at.getTranslateY();
- xtranslation = clipRound(xx);
- ytranslation = clipRound(yy);
- width = clipRound(at.getScaleX() * w + xx) - xtranslation;
- height = clipRound(at.getScaleY() * h + yy) - ytranslation;
- } else {
- xtranslation = x;
- ytranslation = y;
- width = w;
- height = h;
- }
- g.translate(xtranslation, ytranslation);
-
// scaled border
int thickness = (int) Math.ceil(4 * scaleFactor);
@@ -319,12 +284,17 @@ public class MetalBorders {
// midpoint at which highlight & shadow lines
// are positioned on the border
int midPoint = thickness / 2;
+ int stkWidth = clipRound(scaleFactor);
int offset = (((scaleFactor - stkWidth) >= 0) && ((stkWidth % 2) != 0)) ? 1 : 0;
int loc1 = thickness % 2 == 0 ? midPoint + stkWidth / 2 - stkWidth : midPoint;
int loc2 = thickness % 2 == 0 ? midPoint + stkWidth / 2 : midPoint + stkWidth;
// scaled corner
int corner = (int) Math.round(CORNER * scaleFactor);
+ if (g instanceof Graphics2D) {
+ ((Graphics2D) g).setStroke(new BasicStroke((float) stkWidth));
+ }
+
// Draw the Long highlight lines
g.setColor(highlight);
g.drawLine(corner + 1, loc2, width - corner, loc2); //top
@@ -343,14 +313,6 @@ public class MetalBorders {
g.drawLine(corner, (height - offset) - loc2,
width - corner - 1, (height - offset) - loc2);
}
-
- // restore previous transform
- g.translate(-xtranslation, -ytranslation);
- if (resetTransform) {
- Graphics2D g2d = (Graphics2D) g;
- g2d.setTransform(at);
- g2d.setStroke(oldStk);
- }
}
public Insets getBorderInsets(Component c, Insets newInsets) {