diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b9e2745..301c8f83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,9 @@ FlatLaf Change Log - Theme Editor: Fixed occasional empty window on startup on macOS. - FlatLaf window decorations: Fixed black line sometimes painted on top of (native) window border on Windows 11. (issue #852) +- HiDPI: Fixed incomplete component paintings at 125% or 175% scaling on Windows + where sometimes a 1px wide area at the right or bottom component edge is not + repainted. E.g. ScrollPane focus indicator border. (issues #860 and #582) #### Incompatibilities diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonUI.java index 20101854..41691c1b 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonUI.java @@ -29,6 +29,7 @@ import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.Rectangle; +import java.awt.event.FocusEvent; import java.awt.geom.RoundRectangle2D; import java.beans.PropertyChangeEvent; import java.util.Map; @@ -61,6 +62,7 @@ import com.formdev.flatlaf.icons.FlatHelpButtonIcon; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException; +import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.UIScale; @@ -312,11 +314,11 @@ public class FlatButtonUI case BUTTON_TYPE: b.revalidate(); - b.repaint(); + HiDPIUtils.repaint( b ); break; case OUTLINE: - b.repaint(); + HiDPIUtils.repaint( b ); break; case STYLE: @@ -328,7 +330,7 @@ public class FlatButtonUI } else installStyle( b ); b.revalidate(); - b.repaint(); + HiDPIUtils.repaint( b ); break; } } @@ -915,7 +917,7 @@ public class FlatButtonUI @Override public void stateChanged( ChangeEvent e ) { - super.stateChanged( e ); + HiDPIUtils.repaint( b ); // if button is in toolbar, repaint button groups AbstractButton b = (AbstractButton) e.getSource(); @@ -927,5 +929,17 @@ public class FlatButtonUI ((FlatToolBarUI)ui).repaintButtonGroup( b ); } } + + @Override + public void focusGained( FocusEvent e ) { + super.focusGained( e ); + HiDPIUtils.repaint( b ); + } + + @Override + public void focusLost( FocusEvent e ) { + super.focusLost( e ); + HiDPIUtils.repaint( b ); + } } } 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 0d5039cd..863e5c0e 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 @@ -78,6 +78,7 @@ import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; +import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.SystemInfo; @@ -220,7 +221,7 @@ public class FlatComboBoxUI private void repaintArrowButton() { if( arrowButton != null && !comboBox.isEditable() ) - arrowButton.repaint(); + HiDPIUtils.repaint( arrowButton ); } }; comboBox.addMouseListener( hoverListener ); @@ -351,15 +352,15 @@ public class FlatComboBoxUI @Override public void focusGained( FocusEvent e ) { super.focusGained( e ); - if( comboBox != null && comboBox.isEditable() ) - comboBox.repaint(); + if( comboBox != null ) + HiDPIUtils.repaint( comboBox ); } @Override public void focusLost( FocusEvent e ) { super.focusLost( e ); - if( comboBox != null && comboBox.isEditable() ) - comboBox.repaint(); + if( comboBox != null ) + HiDPIUtils.repaint( comboBox ); } }; } @@ -386,12 +387,12 @@ public class FlatComboBoxUI switch( propertyName ) { case PLACEHOLDER_TEXT: if( editor != null ) - editor.repaint(); + HiDPIUtils.repaint( editor ); break; case COMPONENT_ROUND_RECT: case OUTLINE: - comboBox.repaint(); + HiDPIUtils.repaint( comboBox ); break; case MINIMUM_WIDTH: @@ -402,7 +403,7 @@ public class FlatComboBoxUI case STYLE_CLASS: installStyle(); comboBox.revalidate(); - comboBox.repaint(); + HiDPIUtils.repaint( comboBox ); break; } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatEditorPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatEditorPaneUI.java index 2614ddee..cd94679c 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatEditorPaneUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatEditorPaneUI.java @@ -171,7 +171,7 @@ public class FlatEditorPaneUI case FlatClientProperties.STYLE_CLASS: installStyle.run(); c.revalidate(); - c.repaint(); + HiDPIUtils.repaint( c ); break; } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatLabelUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatLabelUI.java index 96fe1212..d898333d 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatLabelUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatLabelUI.java @@ -124,7 +124,7 @@ public class FlatLabelUI } else installStyle( label ); label.revalidate(); - label.repaint(); + HiDPIUtils.repaint( label ); } super.propertyChange( e ); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatListUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatListUI.java index 57635398..00c18241 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatListUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatListUI.java @@ -43,6 +43,7 @@ import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.util.Graphics2DProxy; +import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.UIScale; @@ -182,7 +183,7 @@ public class FlatListUI case FlatClientProperties.STYLE_CLASS: installStyle(); list.revalidate(); - list.repaint(); + HiDPIUtils.repaint( list ); break; } }; @@ -205,7 +206,7 @@ public class FlatListUI Rectangle r = getCellBounds( list, firstIndex, lastIndex ); if( r != null ) { int arc = (int) Math.ceil( UIScale.scale( selectionArc / 2f ) ); - list.repaint( r.x - arc, r.y - arc, r.width + (arc * 2), r.height + (arc * 2) ); + HiDPIUtils.repaint( list, r.x - arc, r.y - arc, r.width + (arc * 2), r.height + (arc * 2) ); } } }; diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuUI.java index 2b7a8c5c..a3015341 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuUI.java @@ -47,6 +47,7 @@ import javax.swing.plaf.basic.BasicMenuUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; +import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.LoggingFacade; /** @@ -167,7 +168,7 @@ public class FlatMenuUI JMenu menu = (JMenu) e.getSource(); if( menu.isTopLevelMenu() && menu.isRolloverEnabled() ) { menu.getModel().setRollover( rollover ); - menu.repaint(); + HiDPIUtils.repaint( menu ); } } }; diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPanelUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPanelUI.java index 5b3cc55d..e1d311e8 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPanelUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPanelUI.java @@ -31,6 +31,7 @@ import javax.swing.plaf.basic.BasicPanelUI; import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; +import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.UIScale; @@ -111,7 +112,7 @@ public class FlatPanelUI } else installStyle( c ); c.revalidate(); - c.repaint(); + HiDPIUtils.repaint( c ); break; case FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER: diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPasswordFieldUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPasswordFieldUI.java index ad0b5538..cf35ed96 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPasswordFieldUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPasswordFieldUI.java @@ -43,6 +43,7 @@ import javax.swing.text.View; import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.icons.FlatCapsLockIcon; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; +import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.UIScale; /** @@ -163,7 +164,7 @@ public class FlatPasswordFieldUI } private void repaint( KeyEvent e ) { if( e.getKeyCode() == KeyEvent.VK_CAPS_LOCK ) { - e.getComponent().repaint(); + HiDPIUtils.repaint( e.getComponent() ); scrollCaretToVisible(); } } @@ -326,7 +327,7 @@ public class FlatPasswordFieldUI if( visible != revealButton.isVisible() ) { revealButton.setVisible( visible ); c.revalidate(); - c.repaint(); + HiDPIUtils.repaint( c ); if( !visible ) { revealButton.setSelected( false ); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatProgressBarUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatProgressBarUI.java index 6e22376d..00940992 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatProgressBarUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatProgressBarUI.java @@ -133,14 +133,14 @@ public class FlatProgressBarUI case PROGRESS_BAR_LARGE_HEIGHT: case PROGRESS_BAR_SQUARE: progressBar.revalidate(); - progressBar.repaint(); + HiDPIUtils.repaint( progressBar ); break; case STYLE: case STYLE_CLASS: installStyle(); progressBar.revalidate(); - progressBar.repaint(); + HiDPIUtils.repaint( progressBar ); break; } }; @@ -294,6 +294,6 @@ public class FlatProgressBarUI // Only solution is to repaint whole progress bar. double systemScaleFactor = UIScale.getSystemScaleFactor( progressBar.getGraphicsConfiguration() ); if( (int) systemScaleFactor != systemScaleFactor ) - progressBar.repaint(); + HiDPIUtils.repaint( progressBar ); } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonUI.java index 155c9fa1..73706178 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonUI.java @@ -47,6 +47,7 @@ import com.formdev.flatlaf.icons.FlatCheckBoxIcon; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException; +import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.UIScale; @@ -173,7 +174,7 @@ public class FlatRadioButtonUI } else installStyle( b ); b.revalidate(); - b.repaint(); + HiDPIUtils.repaint( b ); break; } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollBarUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollBarUI.java index 6f6becc0..8e85860c 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollBarUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollBarUI.java @@ -43,6 +43,7 @@ import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; +import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.UIScale; @@ -212,14 +213,14 @@ public class FlatScrollBarUI switch( e.getPropertyName() ) { case FlatClientProperties.SCROLL_BAR_SHOW_BUTTONS: scrollbar.revalidate(); - scrollbar.repaint(); + HiDPIUtils.repaint( scrollbar ); break; case FlatClientProperties.STYLE: case FlatClientProperties.STYLE_CLASS: installStyle(); scrollbar.revalidate(); - scrollbar.repaint(); + HiDPIUtils.repaint( scrollbar ); break; case "componentOrientation": @@ -492,7 +493,7 @@ public class FlatScrollBarUI private void repaint() { if( scrollbar.isEnabled() ) - scrollbar.repaint(); + HiDPIUtils.repaint( scrollbar ); } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java index f563af65..d851b23b 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java @@ -55,6 +55,7 @@ import javax.swing.plaf.basic.BasicScrollPaneUI; import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; +import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.UIScale; @@ -297,11 +298,11 @@ public class FlatScrollPaneUI JScrollBar hsb = scrollpane.getHorizontalScrollBar(); if( vsb != null ) { vsb.revalidate(); - vsb.repaint(); + HiDPIUtils.repaint( vsb ); } if( hsb != null ) { hsb.revalidate(); - hsb.repaint(); + HiDPIUtils.repaint( hsb ); } break; @@ -321,14 +322,14 @@ public class FlatScrollPaneUI break; case FlatClientProperties.OUTLINE: - scrollpane.repaint(); + HiDPIUtils.repaint( scrollpane ); break; case FlatClientProperties.STYLE: case FlatClientProperties.STYLE_CLASS: installStyle(); scrollpane.revalidate(); - scrollpane.repaint(); + HiDPIUtils.repaint( scrollpane ); break; case "border": @@ -339,7 +340,7 @@ public class FlatScrollPaneUI borderShared = null; installStyle(); scrollpane.revalidate(); - scrollpane.repaint(); + HiDPIUtils.repaint( scrollpane ); } break; } @@ -538,14 +539,14 @@ public class FlatScrollPaneUI public void focusGained( FocusEvent e ) { // necessary to update focus border if( scrollpane.getBorder() instanceof FlatBorder ) - scrollpane.repaint(); + HiDPIUtils.repaint( scrollpane ); } @Override public void focusLost( FocusEvent e ) { // necessary to update focus border if( scrollpane.getBorder() instanceof FlatBorder ) - scrollpane.repaint(); + HiDPIUtils.repaint( scrollpane ); } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSeparatorUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSeparatorUI.java index b93ce6b9..6334df53 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSeparatorUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSeparatorUI.java @@ -32,6 +32,7 @@ import javax.swing.plaf.basic.BasicSeparatorUI; import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; +import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.LoggingFacade; /** @@ -134,7 +135,7 @@ public class FlatSeparatorUI } else installStyle( s ); s.revalidate(); - s.repaint(); + HiDPIUtils.repaint( s ); break; } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSliderUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSliderUI.java index c0a0c49b..7870f054 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSliderUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatSliderUI.java @@ -25,6 +25,8 @@ import java.awt.Graphics2D; import java.awt.Insets; import java.awt.Rectangle; import java.awt.Shape; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; import java.awt.event.MouseEvent; import java.awt.geom.Ellipse2D; import java.awt.geom.Path2D; @@ -191,6 +193,23 @@ public class FlatSliderUI return new FlatTrackListener(); } + @Override + protected FocusListener createFocusListener( JSlider slider ) { + return new BasicSliderUI.FocusHandler() { + @Override + public void focusGained( FocusEvent e ) { + super.focusGained( e ); + HiDPIUtils.repaint( slider ); + } + + @Override + public void focusLost( FocusEvent e ) { + super.focusLost( e ); + HiDPIUtils.repaint( slider ); + } + }; + } + @Override protected PropertyChangeListener createPropertyChangeListener( JSlider slider ) { return FlatStylingSupport.createPropertyChangeListener( slider, this::installStyle, @@ -579,15 +598,15 @@ debug*/ @Override public void setThumbLocation( int x, int y ) { + // set new thumb location and compute union of old and new thumb bounds + Rectangle r = new Rectangle( thumbRect ); + thumbRect.setLocation( x, y ); + SwingUtilities.computeUnion( thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height, r ); + if( !isRoundThumb() ) { // the needle of the directional thumb is painted outside of thumbRect // --> must increase repaint rectangle - // set new thumb location and compute union of old and new thumb bounds - Rectangle r = new Rectangle( thumbRect ); - thumbRect.setLocation( x, y ); - SwingUtilities.computeUnion( thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height, r ); - // increase union rectangle for repaint int extra = (int) Math.ceil( UIScale.scale( focusWidth ) * 0.4142f ); if( slider.getOrientation() == JSlider.HORIZONTAL ) @@ -597,10 +616,9 @@ debug*/ if( !slider.getComponentOrientation().isLeftToRight() ) r.x -= extra; } + } - slider.repaint( r ); - } else - super.setThumbLocation( x, y ); + HiDPIUtils.repaint( slider, r ); } //---- class FlatTrackListener -------------------------------------------- @@ -688,21 +706,21 @@ debug*/ !UIManager.getBoolean( "Slider.snapToTicksOnReleased" ) ) { calculateThumbLocation(); - slider.repaint(); + HiDPIUtils.repaint( slider ); } } protected void setThumbHover( boolean hover ) { if( hover != thumbHover ) { thumbHover = hover; - slider.repaint( thumbRect ); + HiDPIUtils.repaint( slider, thumbRect ); } } protected void setThumbPressed( boolean pressed ) { if( pressed != thumbPressed ) { thumbPressed = pressed; - slider.repaint( thumbRect ); + HiDPIUtils.repaint( slider, thumbRect ); } } 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 d25ae66d..019f8691 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 @@ -47,6 +47,7 @@ import javax.swing.plaf.basic.BasicSpinnerUI; import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; +import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.LoggingFacade; /** @@ -586,7 +587,7 @@ public class FlatSpinnerUI @Override public void focusGained( FocusEvent e ) { // necessary to update focus border - spinner.repaint(); + HiDPIUtils.repaint( spinner ); // if spinner gained focus, transfer it to the editor text field if( e.getComponent() == spinner ) { @@ -599,7 +600,7 @@ public class FlatSpinnerUI @Override public void focusLost( FocusEvent e ) { // necessary to update focus border - spinner.repaint(); + HiDPIUtils.repaint( spinner ); } //---- interface PropertyChangeListener ---- @@ -614,7 +615,7 @@ public class FlatSpinnerUI case FlatClientProperties.COMPONENT_ROUND_RECT: case FlatClientProperties.OUTLINE: - spinner.repaint(); + HiDPIUtils.repaint( spinner ); break; case FlatClientProperties.MINIMUM_WIDTH: @@ -625,7 +626,7 @@ public class FlatSpinnerUI case FlatClientProperties.STYLE_CLASS: installStyle(); spinner.revalidate(); - spinner.repaint(); + HiDPIUtils.repaint( spinner ); break; } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStylingSupport.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStylingSupport.java index de35b6c3..e8c8bff8 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStylingSupport.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStylingSupport.java @@ -40,6 +40,7 @@ import javax.swing.UIManager; import javax.swing.border.Border; import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.StringUtils; import com.formdev.flatlaf.util.SystemInfo; @@ -709,7 +710,7 @@ public class FlatStylingSupport case FlatClientProperties.STYLE_CLASS: installStyle.run(); c.revalidate(); - c.repaint(); + HiDPIUtils.repaint( c ); break; } }; diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java index 8f9a9093..0d65f17f 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java @@ -98,6 +98,7 @@ import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException; import com.formdev.flatlaf.util.Animator; import com.formdev.flatlaf.util.CubicBezierEasing; +import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.JavaCompatibility; import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.StringUtils; @@ -895,7 +896,7 @@ public class FlatTabbedPaneUI } } - tabPane.repaint( r ); + HiDPIUtils.repaint( tabPane, r ); } private boolean inCalculateEqual; @@ -2581,19 +2582,19 @@ debug*/ @Override public void popupMenuWillBecomeVisible( PopupMenuEvent e ) { popupVisible = true; - repaint(); + HiDPIUtils.repaint( this ); } @Override public void popupMenuWillBecomeInvisible( PopupMenuEvent e ) { popupVisible = false; - repaint(); + HiDPIUtils.repaint( this ); } @Override public void popupMenuCanceled( PopupMenuEvent e ) { popupVisible = false; - repaint(); + HiDPIUtils.repaint( this ); } } @@ -3102,7 +3103,7 @@ debug*/ case TABBED_PANE_SHOW_TAB_SEPARATORS: case TABBED_PANE_TAB_TYPE: - tabPane.repaint(); + HiDPIUtils.repaint( tabPane ); break; case TABBED_PANE_SHOW_CONTENT_SEPARATOR: @@ -3125,14 +3126,14 @@ debug*/ case TABBED_PANE_TAB_ICON_PLACEMENT: case TABBED_PANE_TAB_CLOSABLE: tabPane.revalidate(); - tabPane.repaint(); + HiDPIUtils.repaint( tabPane ); break; case TABBED_PANE_LEADING_COMPONENT: uninstallLeadingComponent(); installLeadingComponent(); tabPane.revalidate(); - tabPane.repaint(); + HiDPIUtils.repaint( tabPane ); ensureSelectedTabIsVisibleLater(); break; @@ -3140,7 +3141,7 @@ debug*/ uninstallTrailingComponent(); installTrailingComponent(); tabPane.revalidate(); - tabPane.repaint(); + HiDPIUtils.repaint( tabPane ); ensureSelectedTabIsVisibleLater(); break; @@ -3148,7 +3149,7 @@ debug*/ case STYLE_CLASS: installStyle(); tabPane.revalidate(); - tabPane.repaint(); + HiDPIUtils.repaint( tabPane ); break; } } @@ -3172,7 +3173,7 @@ debug*/ case TABBED_PANE_TAB_ALIGNMENT: case TABBED_PANE_TAB_CLOSABLE: tabPane.revalidate(); - tabPane.repaint(); + HiDPIUtils.repaint( tabPane ); break; } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableHeaderUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableHeaderUI.java index 91184b6f..e02d44c2 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableHeaderUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableHeaderUI.java @@ -45,6 +45,7 @@ import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; +import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.UIScale; @@ -234,8 +235,8 @@ public class FlatTableHeaderUI @Override protected void rolloverColumnUpdated( int oldColumn, int newColumn ) { - header.repaint( header.getHeaderRect( oldColumn ) ); - header.repaint( header.getHeaderRect( newColumn ) ); + HiDPIUtils.repaint( header, header.getHeaderRect( oldColumn ) ); + HiDPIUtils.repaint( header, header.getHeaderRect( newColumn ) ); } @Override diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableUI.java index a7b032eb..b7c20bcd 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableUI.java @@ -56,6 +56,7 @@ import com.formdev.flatlaf.icons.FlatCheckBoxIcon; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.util.Graphics2DProxy; +import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.UIScale; @@ -257,7 +258,7 @@ public class FlatTableUI case FlatClientProperties.STYLE_CLASS: installStyle(); table.revalidate(); - table.repaint(); + HiDPIUtils.repaint( table ); break; } }; @@ -560,7 +561,7 @@ public class FlatTableUI public void componentHidden( ComponentEvent e ) { Container viewport = SwingUtilities.getUnwrappedParent( table ); if( viewport instanceof JViewport ) - viewport.repaint(); + HiDPIUtils.repaint( viewport ); } @Override @@ -579,7 +580,7 @@ public class FlatTableUI int viewportHeight = viewport.getHeight(); int tableHeight = table.getHeight(); if( tableHeight < viewportHeight ) - viewport.repaint( 0, tableHeight, viewport.getWidth(), viewportHeight - tableHeight ); + HiDPIUtils.repaint( viewport, 0, tableHeight, viewport.getWidth(), viewportHeight - tableHeight ); } } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java index 4211da32..88a178ac 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java @@ -239,7 +239,7 @@ public class FlatTextFieldUI case COMPONENT_ROUND_RECT: case OUTLINE: case TEXT_FIELD_PADDING: - c.repaint(); + HiDPIUtils.repaint( c ); break; case MINIMUM_WIDTH: @@ -250,38 +250,38 @@ public class FlatTextFieldUI case STYLE_CLASS: installStyle(); c.revalidate(); - c.repaint(); + HiDPIUtils.repaint( c ); break; case TEXT_FIELD_LEADING_ICON: leadingIcon = (e.getNewValue() instanceof Icon) ? (Icon) e.getNewValue() : null; - c.repaint(); + HiDPIUtils.repaint( c ); break; case TEXT_FIELD_TRAILING_ICON: trailingIcon = (e.getNewValue() instanceof Icon) ? (Icon) e.getNewValue() : null; - c.repaint(); + HiDPIUtils.repaint( c ); break; case TEXT_FIELD_LEADING_COMPONENT: uninstallLeadingComponent(); installLeadingComponent(); c.revalidate(); - c.repaint(); + HiDPIUtils.repaint( c ); break; case TEXT_FIELD_TRAILING_COMPONENT: uninstallTrailingComponent(); installTrailingComponent(); c.revalidate(); - c.repaint(); + HiDPIUtils.repaint( c ); break; case TEXT_FIELD_SHOW_CLEAR_BUTTON: uninstallClearButton(); installClearButton(); c.revalidate(); - c.repaint(); + HiDPIUtils.repaint( c ); break; case "enabled": @@ -815,7 +815,7 @@ debug*/ if( visible != clearButton.isVisible() ) { clearButton.setVisible( visible ); c.revalidate(); - c.repaint(); + HiDPIUtils.repaint( c ); } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToggleButtonUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToggleButtonUI.java index 9859b138..d6a97b61 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToggleButtonUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToggleButtonUI.java @@ -26,6 +26,7 @@ import javax.swing.*; import javax.swing.plaf.ComponentUI; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException; +import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.UIScale; /** @@ -159,14 +160,14 @@ public class FlatToggleButtonUI b.revalidate(); } - b.repaint(); + HiDPIUtils.repaint( b ); break; case TAB_BUTTON_UNDERLINE_PLACEMENT: case TAB_BUTTON_UNDERLINE_HEIGHT: case TAB_BUTTON_UNDERLINE_COLOR: case TAB_BUTTON_SELECTED_BACKGROUND: - b.repaint(); + HiDPIUtils.repaint( b ); break; } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarSeparatorUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarSeparatorUI.java index cae180bd..7eeaea32 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarSeparatorUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarSeparatorUI.java @@ -36,6 +36,7 @@ import javax.swing.plaf.basic.BasicToolBarSeparatorUI; import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; +import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.LoggingFacade; /** @@ -131,7 +132,7 @@ public class FlatToolBarSeparatorUI } else installStyle( s ); s.revalidate(); - s.repaint(); + HiDPIUtils.repaint( s ); break; } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarUI.java index 7bc13fb8..760202c3 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarUI.java @@ -47,6 +47,7 @@ import javax.swing.plaf.basic.BasicToolBarUI; import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; +import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.UIScale; @@ -443,7 +444,7 @@ public class FlatToolBarUI // repaint button group if( gr != null ) - toolBar.repaint( gr ); + HiDPIUtils.repaint(toolBar, gr ); } private ButtonGroup getButtonGroup( AbstractButton b ) { diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTreeUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTreeUI.java index 31d898e2..6eecb751 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTreeUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTreeUI.java @@ -47,6 +47,7 @@ import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.TreePath; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; +import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.UIScale; @@ -310,7 +311,7 @@ public class FlatTreeUI switch( e.getPropertyName() ) { case TREE_WIDE_SELECTION: case TREE_PAINT_SELECTION: - tree.repaint(); + HiDPIUtils.repaint( tree ); break; case "dropLocation": @@ -325,7 +326,7 @@ public class FlatTreeUI case STYLE_CLASS: installStyle(); tree.revalidate(); - tree.repaint(); + HiDPIUtils.repaint( tree ); break; case "enabled": @@ -353,7 +354,7 @@ public class FlatTreeUI Rectangle r = tree.getPathBounds( loc.getPath() ); if( r != null ) - tree.repaint( 0, r.y, tree.getWidth(), r.height ); + HiDPIUtils.repaint( tree, 0, r.y, tree.getWidth(), r.height ); } @Override @@ -370,14 +371,14 @@ public class FlatTreeUI { if( changedPaths.length > 4 ) { // same is done in BasicTreeUI.Handler.valueChanged() - tree.repaint(); + HiDPIUtils.repaint( tree ); } else { int arc = (int) Math.ceil( UIScale.scale( selectionArc / 2f ) ); for( TreePath path : changedPaths ) { Rectangle r = getPathBounds( tree, path ); if( r != null ) - tree.repaint( r.x, r.y - arc, r.width, r.height + (arc * 2) ); + HiDPIUtils.repaint( tree, r.x, r.y - arc, r.width, r.height + (arc * 2) ); } } } 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 115185ca..a6589189 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 @@ -1342,13 +1342,13 @@ debug*/ @Override public void focusGained( FocusEvent e ) { if( repaintCondition == null || repaintCondition.test( repaintComponent ) ) - repaintComponent.repaint(); + HiDPIUtils.repaint( repaintComponent ); } @Override public void focusLost( FocusEvent e ) { if( repaintCondition == null || repaintCondition.test( repaintComponent ) ) - repaintComponent.repaint(); + HiDPIUtils.repaint( repaintComponent ); } } 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 index b4bccd64..4b6f8274 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/HiDPIUtils.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/HiDPIUtils.java @@ -16,9 +16,11 @@ package com.formdev.flatlaf.util; +import java.awt.Component; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; +import java.awt.Rectangle; import java.awt.font.GlyphVector; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; @@ -322,4 +324,164 @@ public class HiDPIUtils } }; } + + /** + * Repaints the given component. + *
+ * See {@link #repaint(Component, int, int, int, int)} for more details. + * + * @since 3.5 + */ + public static void repaint( Component c ) { + repaint( c, 0, 0, c.getWidth(), c.getHeight() ); + } + + /** + * Repaints the given component area. + *
+ * See {@link #repaint(Component, int, int, int, int)} for more details. + * + * @since 3.5 + */ + public static void repaint( Component c, Rectangle r ) { + repaint( c, r.x, r.y, r.width, r.height ); + } + + /** + * Repaints the given component area. + *
+ * Invokes {@link Component#repaint(int, int, int, int)} on the given component, + *
+ * Use this method, instead of {@code Component.repaint(...)}, + * to fix a problem in Swing when using scale factors that end on .25 or .75 + * (e.g. 1.25, 1.75, 2.25, etc) and repainting single components, which may not + * repaint right and/or bottom 1px edge of component. + *
+ * The problem may occur under following conditions: + *
+ * The component is first painted to an in-memory image, + * and then that image is copied to the screen. + * See {@code javax.swing.RepaintManager.PaintManager#paintDoubleBufferedFPScales()}. + *
+ * There are two clipping rectangles involved when copying the image to the screen: + * {@code sun.java2d.SunGraphics2D#devClip} and + * {@code sun.java2d.SunGraphics2D#usrClip}. + *
+ * {@code devClip} is the device clipping in physical pixels. + * It gets the bounds of the painting component, which is either the passed component, + * or if it is non-opaque, then the first opaque ancestor of the passed component. + * It is calculated in {@code sun.java2d.SunGraphics2D#constrain()} while + * getting a graphics context via {@link JComponent#getGraphics()}. + *
+ * {@code usrClip} is the user clipping, which is set via {@link Graphics} clipping methods. + * This is done in {@code javax.swing.RepaintManager.PaintManager#paintDoubleBufferedFPScales()}. + *
+ * The intersection of {@code devClip} and {@code usrClip} + * (computed in {@code sun.java2d.SunGraphics2D#validateCompClip()}) + * is used to copy the image to the screen. + *
+ * Unfortunately different scaling/rounding strategies are used to calculate + * the two clipping rectangles, which is the reason of the issue. + *
+ * {@code devClip} (see {@code sun.java2d.SunGraphics2D#constrain()}): + *
{@code
+ * int devX = (int) (x * scale);
+ * int devWidth = Math.round( width * scale )
+ * }
+ * {@code usrClip} (see {@code javax.swing.RepaintManager.PaintManager#paintDoubleBufferedFPScales()}):
+ * {@code
+ * int usrX = (int) Math.ceil( (x * scale) - 0.5 );
+ * int usrWidth = ((int) Math.ceil( ((x + width) * scale) - 0.5 )) - usrX;
+ * }
+ * X/Y coordinates are always round down for {@code devClip}, but round up for {@code usrClip}.
+ * Width/height calculation is also different.
+ */
+ private static boolean needsSpecialRepaint( Component c, int x, int y, int width, int height ) {
+ // no special repaint necessary for Java 8 or for macOS and Linux
+ // (Java on those platforms does not support fractional scale factors)
+ if( !SystemInfo.isJava_9_orLater || !SystemInfo.isWindows )
+ return false;
+
+ // check whether repaint area is empty or no component given
+ // (same checks as in javax.swing.RepaintManager.addDirtyRegion0())
+ if( width <= 0 || height <= 0 || c == null )
+ return false;
+
+ // check whether component has zero size
+ // (same checks as in javax.swing.RepaintManager.addDirtyRegion0())
+ int compWidth = c.getWidth();
+ int compHeight = c.getHeight();
+ if( compWidth <= 0 || compHeight <= 0 )
+ return false;
+
+ // check whether repaint area does span to right or bottom component edges
+ // (in this case, {@code devClip} is always larger than {@code usrClip})
+ if( x + width < compWidth && y + height < compHeight )
+ return false;
+
+ // if component is not opaque, Swing uses the first opaque ancestor for painting
+ if( !c.isOpaque() ) {
+ int x2 = x;
+ int y2 = y;
+ for( Component p = c.getParent(); p != null; p = p.getParent() ) {
+ x2 += p.getX();
+ y2 += p.getY();
+ if( p.isOpaque() ) {
+ // check whether repaint area does span to right or bottom edges
+ // of the opaque ancestor component
+ // (in this case, {@code devClip} is always larger than {@code usrClip})
+ if( x2 + width < p.getWidth() && y2 + height < p.getHeight() )
+ return false;
+ break;
+ }
+ }
+ }
+
+ // check whether Special repaint is necessary for current scale factor
+ // (doing this check late because it temporary allocates some memory)
+ double scaleFactor = UIScale.getSystemScaleFactor( c.getGraphicsConfiguration() );
+ double fraction = scaleFactor - (int) scaleFactor;
+ if( fraction == 0 || fraction == 0.5 )
+ return false;
+
+ return true;
+ }
}
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatHiDPITest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatHiDPITest.java
new file mode 100644
index 00000000..9154dfda
--- /dev/null
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatHiDPITest.java
@@ -0,0 +1,386 @@
+/*
+ * Copyright 2024 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
+ *
+ * https://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.testing;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.geom.Rectangle2D;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.function.Supplier;
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import com.formdev.flatlaf.FlatLaf;
+import com.formdev.flatlaf.FlatLightLaf;
+import com.formdev.flatlaf.FlatSystemProperties;
+import com.formdev.flatlaf.util.Graphics2DProxy;
+import com.formdev.flatlaf.util.SystemInfo;
+
+/**
+ * @author Karl Tauber
+ */
+public class FlatHiDPITest
+{
+ private static final double scale = 1.25;
+
+ private final JFrame frame;
+ private final JPanel testPanel;
+
+ private final Insets frameInsets;
+
+ public static void main( String[] args ) {
+ System.setProperty( FlatSystemProperties.USE_WINDOW_DECORATIONS, "false" );
+ System.setProperty( "sun.java2d.uiScale", Double.toString( scale ) );
+
+ System.out.println( "Scale factor: " + scale );
+ for( int x = 0; x <= 100; x++ ) {
+ int devX = devScaleXY( x, scale );
+ int usrX = usrScaleXY( x, scale );
+ if( usrX != devX )
+ System.out.printf( "%d: %d != %d\n", x, devX, usrX );
+
+/*
+ for( int w = 0; w <= 10; w++ ) {
+ int devW = devScaleWH( w, scale );
+ int usrW = usrScaleWH( x, w, scale );
+ if( usrW != devW )
+ System.out.printf( " %d %d: %d != %d\n", x, w, devW, usrW );
+ }
+*/
+ }
+
+ SwingUtilities.invokeLater( () -> {
+ if( !SystemInfo.isJava_9_orLater ) {
+ JOptionPane.showMessageDialog( null, "Use Java 9+" );
+ return;
+ }
+
+ FlatLaf.setGlobalExtraDefaults( Collections.singletonMap( "@accentColor", "#f00" ) );
+ FlatLightLaf.setup();
+
+ UIManager.put( "Button.pressedBorderColor", Color.blue );
+ UIManager.put( "TextField.caretBlinkRate", 0 );
+ UIManager.put( "FormattedTextField.caretBlinkRate", 0 );
+
+ new FlatHiDPITest();
+ } );
+ }
+
+ FlatHiDPITest() {
+ frame = new JFrame( "FlatHiDPITest " + scale ) {
+ @Override
+ public Graphics getGraphics() {
+ return TestGraphics2D.install( super.getGraphics(), "JFrame" );
+ }
+ };
+ frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
+
+ // get frame insets
+ frame.addNotify();
+ frameInsets = frame.getInsets();
+
+ testPanel = new JPanel( null ) {
+ @Override
+ public Graphics getGraphics() {
+ return TestGraphics2D.install( super.getGraphics(), "JPanel" );
+ }
+ };
+
+ int y = 0;
+ addAtProblematicXY( 0, y, 40, 16, 48, "TestComp", TestComp::new );
+ y += 20;
+ addAtProblematicXY( 0, y, 40, 16, 48, "JButton", () -> new JButton( "B" ) );
+ y += 20;
+ addAtProblematicXY( 0, y, 40, 16, 48, "JTextField", () -> new JTextField( "Text" ) );
+ y += 20;
+ addAtProblematicXY( 0, y, 40, 16, 48, "JComboBox", JComboBox