This is an automated email from the ASF dual-hosted git repository. ebakke pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/netbeans.git
The following commit(s) were added to refs/heads/master by this push: new af00a5b [NETBEANS-2646] Improve tabcontrol border appearance under fractional HiDPI scaling af00a5b is described below commit af00a5b578d484cda12e06258fd2830d882e0fee Author: Eirik Bakke <eba...@ultorg.com> AuthorDate: Thu Jun 6 11:36:22 2019 -0400 [NETBEANS-2646] Improve tabcontrol border appearance under fractional HiDPI scaling --- .../swing/plaf/windows8/DPISafeBorder.java | 135 +++++++++++++++++++++ .../swing/plaf/windows8/Windows8LFCustoms.java | 5 +- 2 files changed, 137 insertions(+), 3 deletions(-) diff --git a/platform/o.n.swing.plaf/src/org/netbeans/swing/plaf/windows8/DPISafeBorder.java b/platform/o.n.swing.plaf/src/org/netbeans/swing/plaf/windows8/DPISafeBorder.java new file mode 100644 index 0000000..010c93b --- /dev/null +++ b/platform/o.n.swing.plaf/src/org/netbeans/swing/plaf/windows8/DPISafeBorder.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.swing.plaf.windows8; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Insets; +import java.awt.geom.AffineTransform; +import javax.swing.border.Border; +import javax.swing.border.MatteBorder; + +/** + * A border similar to {@link MatteBorder}, but which avoids visual artifacts from rounding errors + * under non-integral HiDPI scaling factors (e.g. 150%). + * + * @author Eirik Bakke (eba...@ultorg.com) + */ +final class DPISafeBorder implements Border { + private final Insets insets; + private final Color color; + + /** + * Create a new instance with the same semantics as that produced by + * {@link MatteBorder#MatteBorder(int, int, int, int, java.awt.Color)}. + * + * @param color may not be null + */ + public static Border matte(int top, int left, int bottom, int right, Color color) { + return new DPISafeBorder(new Insets(top, left, bottom, right), color); + } + + private DPISafeBorder(Insets insets, Color color) { + if (insets == null) + throw new NullPointerException(); + if (color == null) + throw new NullPointerException(); + this.insets = new Insets(insets.top, insets.left, insets.bottom, insets.right); + this.color = color; + } + + @Override + public void paintBorder(Component c, Graphics g0, int x, int y, int width, int height) { + final Graphics2D g = (Graphics2D) g0; + final Color oldColor = g.getColor(); + final AffineTransform oldTransform = g.getTransform(); + g.translate(x, y); + final AffineTransform tx = g.getTransform(); + final int txType = tx.getType(); + final double scale; + /* On fractional DPI scaling factors, such as 150%, a logical pixel position, e.g. (5,0), + may end up being translated to a non-integral device pixel position, e.g. (7.5, 0). The same + goes for border thicknesses, which are specified in logical pixels. In this method, we do + all calculations and painting in device pixels, to avoid rounding errors causing visible + artifacts. On screens without HiDPI scaling, logical pixel values and device pixel values + are identical, and always integral (whole number) values. */ + final int deviceWidth; + final int deviceHeight; + if (txType == AffineTransform.TYPE_UNIFORM_SCALE || + txType == (AffineTransform.TYPE_UNIFORM_SCALE | AffineTransform.TYPE_TRANSLATION)) + { + // HiDPI scaling is active. + scale = tx.getScaleX(); + /* Round the starting (top-left) position up and the end (bottom-right) position down, + to ensure we are painting the border in an area that will not be painted over by an + adjacent component. */ + int deviceX = (int) Math.ceil(tx.getTranslateX()); + int deviceY = (int) Math.ceil(tx.getTranslateY()); + int deviceXend = (int) (tx.getTranslateX() + width * scale); + int deviceYend = (int) (tx.getTranslateY() + height * scale); + deviceWidth = deviceXend - deviceX; + deviceHeight = deviceYend - deviceY; + /* Deactivate the HiDPI scaling transform so we can do paint operations in the device + pixel coordinate system instead of in the logical coordinate system. */ + g.setTransform(new AffineTransform(1, 0, 0, 1, deviceX, deviceY)); + } else { + scale = 1.0; + deviceWidth = width; + deviceHeight = height; + } + final int deviceLeft = deviceBorderWidth(scale, insets.left); + final int deviceRight = deviceBorderWidth(scale, insets.right); + final int deviceTop = deviceBorderWidth(scale, insets.top); + final int deviceBottom = deviceBorderWidth(scale, insets.bottom); + + g.setColor(color); + + // Top border. + g.fillRect(0, 0, deviceWidth - deviceRight, deviceTop); + // Left border. + g.fillRect(0, deviceTop, deviceLeft, deviceHeight - deviceTop); + // Bottom border. + g.fillRect(deviceLeft, deviceHeight - deviceBottom, deviceWidth - deviceLeft, deviceBottom); + // Right border. + g.fillRect(deviceWidth - deviceRight, 0, deviceRight, deviceHeight - deviceBottom); + + g.setTransform(oldTransform); + g.setColor(oldColor); + } + + private int deviceBorderWidth(double scale, int logical) { + if (logical <= 0) + return 0; + return Math.max(1, (int) (scale * logical)); + } + + @Override + public Insets getBorderInsets(Component c) { + return new Insets(insets.top, insets.left, insets.bottom, insets.right); + } + + @Override + public boolean isBorderOpaque() { + /* Set this to false to be safe, since we might not fill in the entire designated logical + area due to rounding errors in the conversion to device pixels. */ + return false; + } +} diff --git a/platform/o.n.swing.plaf/src/org/netbeans/swing/plaf/windows8/Windows8LFCustoms.java b/platform/o.n.swing.plaf/src/org/netbeans/swing/plaf/windows8/Windows8LFCustoms.java index 851ffe7..f86cf5a 100644 --- a/platform/o.n.swing.plaf/src/org/netbeans/swing/plaf/windows8/Windows8LFCustoms.java +++ b/platform/o.n.swing.plaf/src/org/netbeans/swing/plaf/windows8/Windows8LFCustoms.java @@ -22,7 +22,6 @@ package org.netbeans.swing.plaf.windows8; import java.awt.*; import javax.swing.*; import javax.swing.border.EmptyBorder; -import javax.swing.border.MatteBorder; import javax.swing.plaf.ColorUIResource; import org.netbeans.swing.plaf.LFCustoms; import org.netbeans.swing.plaf.util.GuaranteedValue; @@ -223,12 +222,12 @@ public final class Windows8LFCustoms extends LFCustoms { //Borders for the tab control EDITOR_TAB_OUTER_BORDER, BorderFactory.createEmptyBorder(), EDITOR_TAB_CONTENT_BORDER, - new MatteBorder(0, 1, 1, 1, new Color(137, 140, 149)), + DPISafeBorder.matte(0, 1, 1, 1, new Color(137, 140, 149)), EDITOR_TAB_TABS_BORDER, BorderFactory.createEmptyBorder(), VIEW_TAB_OUTER_BORDER, BorderFactory.createEmptyBorder(), VIEW_TAB_CONTENT_BORDER, - new MatteBorder(0, 1, 1, 1, new Color(137, 140, 149)), + DPISafeBorder.matte(0, 1, 1, 1, new Color(137, 140, 149)), VIEW_TAB_TABS_BORDER, BorderFactory.createEmptyBorder(), }; } --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@netbeans.apache.org For additional commands, e-mail: commits-h...@netbeans.apache.org For further information about the NetBeans mailing lists, visit: https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists