mirror of
https://github.com/JFormDesigner/FlatLaf.git
synced 2025-12-27 03:46:17 -06:00
Merge PR #864: HiDPI: fix incomplete component repainting at 125% or 175% scaling on Windows
This commit is contained in:
@@ -34,6 +34,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
|
||||
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ public class FlatEditorPaneUI
|
||||
case FlatClientProperties.STYLE_CLASS:
|
||||
installStyle.run();
|
||||
c.revalidate();
|
||||
c.repaint();
|
||||
HiDPIUtils.repaint( c );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ public class FlatLabelUI
|
||||
} else
|
||||
installStyle( label );
|
||||
label.revalidate();
|
||||
label.repaint();
|
||||
HiDPIUtils.repaint( label );
|
||||
}
|
||||
|
||||
super.propertyChange( e );
|
||||
|
||||
@@ -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) );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -66,6 +66,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;
|
||||
@@ -297,7 +298,7 @@ public class FlatTableUI
|
||||
case FlatClientProperties.STYLE_CLASS:
|
||||
installStyle();
|
||||
table.revalidate();
|
||||
table.repaint();
|
||||
HiDPIUtils.repaint( table );
|
||||
break;
|
||||
}
|
||||
};
|
||||
@@ -912,7 +913,7 @@ public class FlatTableUI
|
||||
public void componentHidden( ComponentEvent e ) {
|
||||
Container viewport = SwingUtilities.getUnwrappedParent( table );
|
||||
if( viewport instanceof JViewport )
|
||||
viewport.repaint();
|
||||
HiDPIUtils.repaint( viewport );
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -931,7 +932,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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ) {
|
||||
|
||||
@@ -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) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1347,13 +1347,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 );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,14 +16,17 @@
|
||||
|
||||
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;
|
||||
import java.text.AttributedCharacterIterator;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.RepaintManager;
|
||||
import com.formdev.flatlaf.FlatSystemProperties;
|
||||
|
||||
/**
|
||||
@@ -322,4 +325,243 @@ public class HiDPIUtils
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Repaints the given component.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* Invokes {@link Component#repaint(int, int, int, int)} on the given component,
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* The problem may occur under following conditions:
|
||||
* <ul>
|
||||
* <li>using Java 9 or later
|
||||
* <li>system scale factor is 125%, 175%, 225%, ...
|
||||
* (Windows only; Java on macOS and Linux does not support fractional scale factors)
|
||||
* <li>repaint whole component or right/bottom area of component
|
||||
* <li>component is opaque; or component is contained in a opaque container
|
||||
* that has same right/bottom bounds as component
|
||||
* <li>component has bounds that Java/Swing scales different when repainting components
|
||||
* </ul>
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
public static void repaint( Component c, int x, int y, int width, int height ) {
|
||||
// repaint given component area
|
||||
// Always invoke repaint() on given component, even if also invoked (below)
|
||||
// on one of its ancestors, for the case that component overrides that method.
|
||||
// Also RepaintManager "merges" the two repaints into one.
|
||||
c.repaint( x, y, width, height );
|
||||
|
||||
if( RepaintManager.currentManager( c ) instanceof HiDPIRepaintManager )
|
||||
return;
|
||||
|
||||
// if necessary, also repaint given area in first ancestor that is larger than component
|
||||
// to avoid clipping issue (see needsSpecialRepaint())
|
||||
if( needsSpecialRepaint( c, x, y, width, height ) ) {
|
||||
int x2 = x + c.getX();
|
||||
int y2 = y + c.getY();
|
||||
for( Component p = c.getParent(); p != null; p = p.getParent() ) {
|
||||
x2 += p.getX();
|
||||
y2 += p.getY();
|
||||
if( x2 + width < p.getWidth() && y2 + height < p.getHeight() ) {
|
||||
p.repaint( x2, y2, width, height );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* There is 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.
|
||||
* <p>
|
||||
* 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()}.
|
||||
* <p>
|
||||
* There are two clipping rectangles involved when copying the image to the screen:
|
||||
* {@code sun.java2d.SunGraphics2D#devClip} and
|
||||
* {@code sun.java2d.SunGraphics2D#usrClip}.
|
||||
* <p>
|
||||
* {@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()}.
|
||||
* <p>
|
||||
* {@code usrClip} is the user clipping, which is set via {@link Graphics} clipping methods.
|
||||
* This is done in {@code javax.swing.RepaintManager.PaintManager#paintDoubleBufferedFPScales()}.
|
||||
* <p>
|
||||
* The intersection of {@code devClip} and {@code usrClip}
|
||||
* (computed in {@code sun.java2d.SunGraphics2D#validateCompClip()})
|
||||
* is used to copy the image to the screen.
|
||||
* <p>
|
||||
* Unfortunately different scaling/rounding strategies are used to calculate
|
||||
* the two clipping rectangles, which is the reason of the issue.
|
||||
* <p>
|
||||
* {@code devClip} (see {@code sun.java2d.SunGraphics2D#constrain()}):
|
||||
* <pre>{@code
|
||||
* int devX = (int) (x * scale);
|
||||
* int devWidth = Math.round( width * scale )
|
||||
* }</pre>
|
||||
* {@code usrClip} (see {@code javax.swing.RepaintManager.PaintManager#paintDoubleBufferedFPScales()}):
|
||||
* <pre>{@code
|
||||
* int usrX = (int) Math.ceil( (x * scale) - 0.5 );
|
||||
* int usrWidth = ((int) Math.ceil( ((x + width) * scale) - 0.5 )) - usrX;
|
||||
* }</pre>
|
||||
* X/Y coordinates are always rounded down for {@code devClip}, but rounded 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs a {@link HiDPIRepaintManager} on Windows when running in Java 9+,
|
||||
* but only if default repaint manager is currently installed.
|
||||
* <p>
|
||||
* Invoke once on application startup.
|
||||
* Compatible with all/other L&Fs.
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
public static void installHiDPIRepaintManager() {
|
||||
if( !SystemInfo.isJava_9_orLater || !SystemInfo.isWindows )
|
||||
return;
|
||||
|
||||
RepaintManager manager = RepaintManager.currentManager( (Component) null );
|
||||
if( manager.getClass() == RepaintManager.class )
|
||||
RepaintManager.setCurrentManager( new HiDPIRepaintManager() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link #repaint(Component, int, int, int, int)},
|
||||
* but invokes callback instead of invoking {@link Component#repaint(int, int, int, int)}.
|
||||
* <p>
|
||||
* For use in custom repaint managers.
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
public static void addDirtyRegion( JComponent c, int x, int y, int width, int height, DirtyRegionCallback callback ) {
|
||||
if( needsSpecialRepaint( c, x, y, width, height ) ) {
|
||||
int x2 = x + c.getX();
|
||||
int y2 = y + c.getY();
|
||||
for( Component p = c.getParent(); p != null; p = p.getParent() ) {
|
||||
x2 += p.getX();
|
||||
y2 += p.getY();
|
||||
if( x2 + width < p.getWidth() && y2 + height < p.getHeight() && p instanceof JComponent ) {
|
||||
callback.addDirtyRegion( (JComponent) p, x2, y2, width, height );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callback.addDirtyRegion( c, x, y, width, height );
|
||||
}
|
||||
|
||||
//---- interface DirtyRegionCallback --------------------------------------
|
||||
|
||||
/**
|
||||
* For {@link HiDPIUtils#addDirtyRegion(JComponent, int, int, int, int, DirtyRegionCallback)}.
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
public interface DirtyRegionCallback {
|
||||
void addDirtyRegion( JComponent c, int x, int y, int w, int h );
|
||||
}
|
||||
|
||||
//---- class HiDPIRepaintManager ------------------------------------------
|
||||
|
||||
/**
|
||||
* A repaint manager that fixes a problem in Swing when repainting components
|
||||
* at some scale factors (e.g. 125%, 175%, etc) on Windows.
|
||||
* <p>
|
||||
* Use {@link HiDPIUtils#installHiDPIRepaintManager()} to install it.
|
||||
* <p>
|
||||
* See {@link HiDPIUtils#repaint(Component, int, int, int, int)} for details.
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
public static class HiDPIRepaintManager
|
||||
extends RepaintManager
|
||||
{
|
||||
@Override
|
||||
public void addDirtyRegion( JComponent c, int x, int y, int w, int h ) {
|
||||
HiDPIUtils.addDirtyRegion( c, x, y, w, h, super::addDirtyRegion );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,9 @@ public class FlatLafDemo
|
||||
// use Roboto Mono font
|
||||
// FlatLaf.setPreferredMonospacedFontFamily( FlatRobotoMonoFont.FAMILY );
|
||||
|
||||
// install own repaint manager to fix repaint issues at 125%, 175%, 225%, ... on Windows
|
||||
// HiDPIUtils.installHiDPIRepaintManager();
|
||||
|
||||
// application specific UI defaults
|
||||
FlatLaf.registerCustomDefaultsSource( "com.formdev.flatlaf.demo" );
|
||||
|
||||
|
||||
@@ -0,0 +1,390 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
// HiDPIUtils.installHiDPIRepaintManager();
|
||||
|
||||
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<String>::new );
|
||||
y += 20;
|
||||
addAtProblematicXY( 0, y, 40, 16, 48, "JComboBox editable", () -> {
|
||||
JComboBox<String> c = new JComboBox<>();
|
||||
c.setEditable( true );
|
||||
return c;
|
||||
} );
|
||||
y += 20;
|
||||
addAtProblematicXY( 0, y, 40, 16, 48, "JSpinner", JSpinner::new );
|
||||
y += 20;
|
||||
addAtProblematicXY( 0, y, 80, 16, 88, "JSlider", JSlider::new );
|
||||
y += 20;
|
||||
addAtProblematicXY( 0, y, 80, 16, 88, "JScrollBar", () -> new JScrollBar( JScrollBar.HORIZONTAL ) );
|
||||
y += 20;
|
||||
addAtProblematicXY( 0, y, 16, 40, 20, "JScrollBar", () -> new JScrollBar( JScrollBar.VERTICAL ) );
|
||||
y += 60;
|
||||
addAtProblematicXY( 0, y, 82, 60, 88, "JScrollPane", () -> new JScrollPane( new JTree() ) );
|
||||
y += 80;
|
||||
addAtProblematicXY( 0, y, 80, 16, 88, "JProgressBar", () -> {
|
||||
JProgressBar c = new JProgressBar();
|
||||
c.setValue( 60 );
|
||||
c.addMouseListener( new MouseAdapter() {
|
||||
@Override
|
||||
public void mousePressed( MouseEvent e ) {
|
||||
int value = c.getValue();
|
||||
c.setValue( (value >= 20) ? value - 20 : 100 );
|
||||
}
|
||||
} );
|
||||
return c;
|
||||
} );
|
||||
|
||||
frame.getContentPane().add( testPanel );
|
||||
frame.setSize( 400, 400 );
|
||||
frame.setVisible( true );
|
||||
}
|
||||
|
||||
private void addAtProblematicXY( int x, int y, int w, int h, int offset, String text, Supplier<Component> generator ) {
|
||||
// plain component
|
||||
addAtProblematicXY( x, y, w, h, generator.get() );
|
||||
|
||||
// component in (opaque) panel which has same bounds as component
|
||||
addAtProblematicXY( x + offset, y, w, h, wrapInPanel( generator.get(), false ) );
|
||||
|
||||
// component in (opaque) panel which is 1px larger than component
|
||||
addAtProblematicXY( x + (offset * 2), y, w + 1, h + 1, wrapInPanel( generator.get(), true ) );
|
||||
|
||||
JLabel l = new JLabel( text );
|
||||
testPanel.add( l );
|
||||
l.setLocation( x + (offset * 3) + 20, y );
|
||||
l.setSize( l.getPreferredSize() );
|
||||
}
|
||||
|
||||
private void addAtProblematicXY( int x, int y, int w, int h, Component c ) {
|
||||
int px = nextProblematicXY( x + frameInsets.left ) - frameInsets.left;
|
||||
int py = nextProblematicXY( y + frameInsets.top ) - frameInsets.top;
|
||||
testPanel.add( c );
|
||||
c.setBounds( px, py, w, h );
|
||||
}
|
||||
|
||||
private Component wrapInPanel( Component c, boolean emptyBorder ) {
|
||||
JPanel p = new JPanel( new BorderLayout() ) {
|
||||
@Override
|
||||
public Graphics getGraphics() {
|
||||
return TestGraphics2D.install( super.getGraphics(), "wrapping JPanel" );
|
||||
}
|
||||
};
|
||||
if( emptyBorder )
|
||||
p.setBorder( new EmptyBorder( 0, 0, 1, 1 ) );
|
||||
p.add( c, BorderLayout.CENTER );
|
||||
return p;
|
||||
}
|
||||
|
||||
private static int nextProblematicXY( int xy ) {
|
||||
for( int i = xy; i < xy + 20; i++ ) {
|
||||
if( devScaleXY( i, scale ) != usrScaleXY( i, scale ) )
|
||||
return i;
|
||||
}
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
private static int devScaleXY( int xy, double scale ) {
|
||||
return (int) (xy * scale);
|
||||
}
|
||||
|
||||
private static int usrScaleXY( int xy, double scale ) {
|
||||
// see sun.java2d.pipe.Region.clipRound(double);
|
||||
return (int) Math.ceil( (xy * scale) - 0.5 );
|
||||
}
|
||||
|
||||
@SuppressWarnings( "unused" )
|
||||
private static int devScaleWH( int wh, double scale ) {
|
||||
return (int) Math.round( wh * scale );
|
||||
}
|
||||
|
||||
@SuppressWarnings( "unused" )
|
||||
private static int usrScaleWH( int xy, int wh, double scale ) {
|
||||
int usrXY = usrScaleXY( xy, scale );
|
||||
return ((int) Math.ceil( ((xy + wh) * scale) - 0.5 )) - usrXY;
|
||||
}
|
||||
|
||||
//---- class TestComp -----------------------------------------------------
|
||||
|
||||
private static class TestComp
|
||||
extends JComponent
|
||||
implements FocusListener
|
||||
{
|
||||
// used to avoid repainting when window is deactivated and activated (for easier debugging)
|
||||
private boolean permanentFocused;
|
||||
|
||||
TestComp() {
|
||||
setOpaque( true );
|
||||
setFocusable( true );
|
||||
|
||||
addFocusListener( this );
|
||||
addMouseListener( new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked( MouseEvent e ) {
|
||||
requestFocusInWindow();
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintComponent( Graphics g ) {
|
||||
g.setColor( isFocusOwner() ? Color.green : Color.red );
|
||||
g.fillRect( 0, 0, getWidth(), getHeight() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focusGained( FocusEvent e ) {
|
||||
if( permanentFocused )
|
||||
return;
|
||||
|
||||
if( !e.isTemporary() ) {
|
||||
repaint();
|
||||
permanentFocused = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focusLost( FocusEvent e ) {
|
||||
if( !e.isTemporary() ) {
|
||||
repaint();
|
||||
permanentFocused = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Graphics getGraphics() {
|
||||
return TestGraphics2D.install( super.getGraphics(), "TestComp" );
|
||||
}
|
||||
}
|
||||
|
||||
//---- TestGraphics2D -----------------------------------------------------
|
||||
|
||||
private static class TestGraphics2D
|
||||
extends Graphics2DProxy
|
||||
{
|
||||
private final Graphics2D delegate;
|
||||
private final String id;
|
||||
|
||||
static Graphics install( Graphics g, String id ) {
|
||||
return wasInvokedFrom_safelyGetGraphics()
|
||||
? new TestGraphics2D( (Graphics2D) g, id )
|
||||
: g;
|
||||
}
|
||||
|
||||
private static boolean wasInvokedFrom_safelyGetGraphics() {
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
StackTraceElement stackTraceElement = stackTrace[4];
|
||||
return "javax.swing.JComponent".equals( stackTraceElement.getClassName() ) &&
|
||||
"safelyGetGraphics".equals( stackTraceElement.getMethodName() );
|
||||
}
|
||||
|
||||
private TestGraphics2D( Graphics2D delegate, String id ) {
|
||||
super( delegate );
|
||||
this.delegate = delegate;
|
||||
this.id = id;
|
||||
|
||||
System.out.println();
|
||||
System.out.println( "---------------------------------------- " );
|
||||
System.out.println( id + ": construct" );
|
||||
printClipRects();
|
||||
}
|
||||
|
||||
private void printClipRects() {
|
||||
try {
|
||||
Class<?> sunGraphics2DClass = Class.forName( "sun.java2d.SunGraphics2D" );
|
||||
if( !sunGraphics2DClass.isInstance( delegate ) ) {
|
||||
System.out.println( " not a SunGraphics2D: " + delegate.getClass().getName() );
|
||||
return;
|
||||
}
|
||||
|
||||
Rectangle devClip = region2rect( getFieldValue( sunGraphics2DClass, delegate, "devClip" ) );
|
||||
Shape usrClip = (Shape) getFieldValue( sunGraphics2DClass, delegate, "usrClip" );
|
||||
Rectangle clipRegion = region2rect( getFieldValue( sunGraphics2DClass, delegate, "clipRegion" ) );
|
||||
|
||||
printField( devClip, "devClip" );
|
||||
printField( usrClip, "usrClip" );
|
||||
printField( clipRegion, "clipRegion" );
|
||||
|
||||
if( (usrClip instanceof Rectangle && !devClip.contains( (Rectangle) usrClip )) ||
|
||||
(usrClip instanceof Rectangle2D && !devClip.contains( (Rectangle2D) usrClip )) )
|
||||
{
|
||||
System.out.flush();
|
||||
System.err.println( "WARNING: devClip smaller than usrClip" );
|
||||
System.err.flush();
|
||||
}
|
||||
} catch( Exception ex ) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void printField( Object value, String name ) throws Exception {
|
||||
System.out.printf( " %-16s", name );
|
||||
|
||||
if( value instanceof Rectangle ) {
|
||||
Rectangle r = (Rectangle) value;
|
||||
System.out.printf( "xy %3d %3d -> %3d %3d wh %3d %3d\n",
|
||||
r.x, r.y, r.x + r.width, r.y + r.height, r.width, r.height );
|
||||
} else if( value instanceof Rectangle2D ) {
|
||||
Rectangle2D r = (Rectangle2D) value;
|
||||
System.out.printf( "xy %.2f %.2f -> %.2f %.2f wh %.2f %.2f\n",
|
||||
r.getX(), r.getY(), r.getX() + r.getWidth(), r.getY() + r.getHeight(), r.getWidth(), r.getHeight() );
|
||||
} else
|
||||
System.out.println( value );
|
||||
}
|
||||
|
||||
private static Rectangle region2rect( Object region ) throws Exception {
|
||||
Class<?> regionClass = Class.forName( "sun.java2d.pipe.Region" );
|
||||
int loX = (int) getMethodValue( regionClass, region, "getLoX" );
|
||||
int loY = (int) getMethodValue( regionClass, region, "getLoY" );
|
||||
int hiX = (int) getMethodValue( regionClass, region, "getHiX" );
|
||||
int hiY = (int) getMethodValue( regionClass, region, "getHiY" );
|
||||
return new Rectangle( loX, loY, hiX - loX, hiY - loY );
|
||||
}
|
||||
|
||||
private static Object getFieldValue( Class<?> cls, Object object, String name ) throws Exception {
|
||||
Field f = cls.getDeclaredField( name );
|
||||
f.setAccessible( true );
|
||||
return f.get( object );
|
||||
}
|
||||
|
||||
private static Object getMethodValue( Class<?> cls, Object object, String name ) throws Exception {
|
||||
Method m = cls.getDeclaredMethod( name );
|
||||
m.setAccessible( true );
|
||||
return m.invoke( object );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clipRect( int x, int y, int width, int height ) {
|
||||
System.out.printf( "\n%s: clipRect( %d, %d, %d, %d )\n", id, x, y, width, height );
|
||||
super.clipRect( x, y, width, height );
|
||||
printClipRects();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClip( int x, int y, int width, int height ) {
|
||||
System.out.printf( "\n%s: setClip( %d, %d, %d, %d )\n", id, x, y, width, height );
|
||||
super.setClip( x, y, width, height );
|
||||
printClipRects();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClip( Shape clip ) {
|
||||
System.out.printf( "\n%s: setClip( %s )\n", id, clip );
|
||||
super.setClip( clip );
|
||||
printClipRects();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clip( Shape s ) {
|
||||
System.out.printf( "\n%s: clip( %s )\n", id, s );
|
||||
super.clip( s );
|
||||
printClipRects();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user