diff --git a/CHANGELOG.md b/CHANGELOG.md index aecb9b25..a826b36d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ FlatLaf Change Log - TabbedPane: Support background color for selected tabs. - CheckBox: changed `CheckBox.arc` from radius to diameter to be consistent with `Button.arc` and `Component.arc` +- Fixed clipped borders at 125%, 150% and 175% scaling when outer focus width is + zero (default in "Flat Light" and "Flat Dark" themes). ## 0.21 diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatComboBoxUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatComboBoxUI.java index 2964b41d..476742fe 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatComboBoxUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatComboBoxUI.java @@ -335,7 +335,7 @@ public class FlatComboBoxUI g2.setColor( enabled ? borderColor : disabledBorderColor ); float lw = scale( 1f ); float lx = isLeftToRight ? arrowX : arrowX + arrowWidth - lw; - g2.fill( new Rectangle2D.Float( lx, focusWidth, lw, height - (focusWidth * 2) ) ); + g2.fill( new Rectangle2D.Float( lx, focusWidth, lw, height - 1 - (focusWidth * 2)) ); } paint( g, c ); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSpinnerUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSpinnerUI.java index 696571f9..f64ce2b3 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSpinnerUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSpinnerUI.java @@ -285,7 +285,7 @@ public class FlatSpinnerUI g2.setColor( enabled ? borderColor : disabledBorderColor ); float lw = scale( 1f ); float lx = isLeftToRight ? arrowX : arrowX + arrowWidth - lw; - g2.fill( new Rectangle2D.Float( lx, focusWidth, lw, height - (focusWidth * 2) ) ); + g2.fill( new Rectangle2D.Float( lx, focusWidth, lw, height - 1 - (focusWidth * 2) ) ); paint( g, c ); } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java index fe7a2a51..b29d16bc 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java @@ -39,6 +39,7 @@ import javax.swing.LookAndFeel; import javax.swing.UIManager; import javax.swing.plaf.ColorUIResource; import com.formdev.flatlaf.util.DerivedColor; +import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.JavaCompatibility; import com.formdev.flatlaf.util.UIScale; @@ -136,6 +137,23 @@ public class FlatUIUtils */ public static void paintComponentOuterBorder( Graphics2D g, int x, int y, int width, int height, float focusWidth, float lineWidth, float arc ) + { + double systemScaleFactor = UIScale.getSystemScaleFactor( g ); + if( systemScaleFactor != 1 && systemScaleFactor != 2 ) { + // paint at scale 1x to avoid clipping on right and bottom edges at 125%, 150% or 175% + HiDPIUtils.paintAtScale1x( g, x, y, width, height, systemScaleFactor, + (g2d, x2, y2, width2, height2, scaleFactor) -> { + paintComponentOuterBorderImpl( g2d, x2, y2, width2, height2, + (float) (focusWidth * scaleFactor), (float) (lineWidth * scaleFactor), (float) (arc * scaleFactor) ); + } ); + return; + } + + paintComponentOuterBorderImpl( g, x, y, width, height, focusWidth, lineWidth, arc ); + } + + private static void paintComponentOuterBorderImpl( Graphics2D g, int x, int y, int width, int height, + float focusWidth, float lineWidth, float arc ) { float outerRadius = (arc > 0) ? arc + focusWidth - UIScale.scale( 2f ) : focusWidth; float ow = focusWidth + lineWidth; @@ -159,6 +177,23 @@ public class FlatUIUtils */ public static void paintComponentBorder( Graphics2D g, int x, int y, int width, int height, float focusWidth, float lineWidth, float arc ) + { + double systemScaleFactor = UIScale.getSystemScaleFactor( g ); + if( systemScaleFactor != 1 && systemScaleFactor != 2 ) { + // paint at scale 1x to avoid clipping on right and bottom edges at 125%, 150% or 175% + HiDPIUtils.paintAtScale1x( g, x, y, width, height, systemScaleFactor, + (g2d, x2, y2, width2, height2, scaleFactor) -> { + paintComponentBorderImpl( g2d, x2, y2, width2, height2, + (float) (focusWidth * scaleFactor), (float) (lineWidth * scaleFactor), (float) (arc * scaleFactor) ); + } ); + return; + } + + paintComponentBorderImpl( g, x, y, width, height, focusWidth, lineWidth, arc ); + } + + private static void paintComponentBorderImpl( Graphics2D g, int x, int y, int width, int height, + float focusWidth, float lineWidth, float arc ) { float arc2 = arc > lineWidth ? arc - lineWidth : 0f; @@ -187,6 +222,23 @@ public class FlatUIUtils */ public static void paintComponentBackground( Graphics2D g, int x, int y, int width, int height, float focusWidth, float arc ) + { + double systemScaleFactor = UIScale.getSystemScaleFactor( g ); + if( systemScaleFactor != 1 && systemScaleFactor != 2 ) { + // paint at scale 1x to avoid clipping on right and bottom edges at 125%, 150% or 175% + HiDPIUtils.paintAtScale1x( g, x, y, width, height, systemScaleFactor, + (g2d, x2, y2, width2, height2, scaleFactor) -> { + paintComponentBackgroundImpl( g2d, x2, y2, width2, height2, + (float) (focusWidth * scaleFactor), (float) (arc * scaleFactor) ); + } ); + return; + } + + paintComponentBackgroundImpl( g, x, y, width, height, focusWidth, arc ); + } + + private static void paintComponentBackgroundImpl( Graphics2D g, int x, int y, int width, int height, + float focusWidth, float arc ) { g.fill( new RoundRectangle2D.Float( x + focusWidth, y + focusWidth, diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/HiDPIUtils.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/HiDPIUtils.java new file mode 100644 index 00000000..0e164a6f --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/HiDPIUtils.java @@ -0,0 +1,111 @@ +/* + * Copyright 2019 FormDev Software GmbH + * + * Licensed 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 com.formdev.flatlaf.util; + +import java.awt.Graphics2D; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import javax.swing.JComponent; + +/** + * @author Karl Tauber + */ +public class HiDPIUtils +{ + public interface Painter { + public void paint( Graphics2D g, int x, int y, int width, int height, double scaleFactor ); + } + + public static void paintAtScale1x( Graphics2D g, JComponent c, Painter painter ) { + paintAtScale1x( g, 0, 0, c.getWidth(), c.getHeight(), painter ); + } + + public static void paintAtScale1x( Graphics2D g, int x, int y, int width, int height, Painter painter ) { + paintAtScale1x( g, x, y, width, height, UIScale.getSystemScaleFactor( g ), painter ); + } + + /** + * Paint at system scale factor 1x to avoid rounding issues at 125%, 150% and 175% scaling. + *
+ * Scales the given Graphics2D down to 100% and invokes the + * given painter passing scaled x, y, width and height. + *
+ * Uses the same scaling calculation as the JRE uses. + */ + public static void paintAtScale1x( Graphics2D g, int x, int y, int width, int height, + double scaleFactor, Painter painter ) + { + if( scaleFactor == 1 ) { + painter.paint( g, x, y, width, height, 1 ); + return; + } + + // save original transform + AffineTransform transform = g.getTransform(); + + // scale rectangle + Rectangle2D.Double scaledRect = scale( transform, x, y, width, height ); + + try { + // unscale to factor 1.0 + double scale = 1.0 / scaleFactor; + g.scale( scale, scale ); + + // compute origin delta x/y + double dx = Math.floor( scaledRect.x ) - transform.getTranslateX(); + double dy = Math.floor( scaledRect.y ) - transform.getTranslateY(); + + // move origin to make sure that origin x/y are at whole numbers + if( dx != 0 || dy != 0 ) + g.translate( dx, dy ); + + int swidth = (int) scaledRect.width; + int sheight = (int) scaledRect.height; + + // paint + painter.paint( g, 0, 0, swidth, sheight, scaleFactor ); + } finally { + // restore original transform + g.setTransform( transform ); + } + } + + /** + * Scales a rectangle in the same way as the JRE does in + * sun.java2d.pipe.PixelToParallelogramConverter.fillRectangle(), + * which is used by Graphics.fillRect(). + */ + private static Rectangle2D.Double scale( AffineTransform transform, int x, int y, int width, int height ) { + double dx1 = transform.getScaleX(); + double dy2 = transform.getScaleY(); + double px = x * dx1 + transform.getTranslateX(); + double py = y * dy2 + transform.getTranslateY(); + dx1 *= width; + dy2 *= height; + + double newx = normalize( px ); + double newy = normalize( py ); + dx1 = normalize( px + dx1 ) - newx; + dy2 = normalize( py + dy2 ) - newy; + + return new Rectangle2D.Double( newx, newy, dx1, dy2 ); + } + + private static double normalize( double value ) { + return Math.floor( value + 0.25 ) + 0.25; + } +} diff --git a/flatlaf-swingx/src/main/java/com/formdev/flatlaf/swingx/ui/FlatDatePickerUI.java b/flatlaf-swingx/src/main/java/com/formdev/flatlaf/swingx/ui/FlatDatePickerUI.java index 2d50fb5d..5bff5639 100644 --- a/flatlaf-swingx/src/main/java/com/formdev/flatlaf/swingx/ui/FlatDatePickerUI.java +++ b/flatlaf-swingx/src/main/java/com/formdev/flatlaf/swingx/ui/FlatDatePickerUI.java @@ -255,7 +255,7 @@ public class FlatDatePickerUI g2.setColor( enabled ? borderColor : disabledBorderColor ); float lw = scale( 1f ); float lx = isLeftToRight ? arrowX : arrowX + arrowWidth - lw; - g2.fill( new Rectangle2D.Float( lx, focusWidth, lw, height - (focusWidth * 2) ) ); + g2.fill( new Rectangle2D.Float( lx, focusWidth, lw, height - 1 - (focusWidth * 2) ) ); paint( g, c ); }