Skip to content

Commit

Permalink
ScrollPane: fixed/improved border painting at 125% - 175% scaling to …
Browse files Browse the repository at this point in the history
…avoid different border thicknesses (issue #743)
  • Loading branch information
DevCharly committed Jun 14, 2024
1 parent 2a494b1 commit 0c0d4bf
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 15 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ FlatLaf Change Log
`<big>`, `<small>` and `<samp>` in HTML text for components Button, CheckBox,
RadioButton, MenuItem (and subclasses), JideLabel, JideButton, JXBusyLabel and
JXHyperlink. Also fixed for Label and ToolTip if using Java 11+.
- ScrollPane: Fixed/improved border painting at 125% - 175% scaling to avoid
different border thicknesses. (issue #743)
- Table: Fixed painting of alternating rows below table if auto-resize mode is
`JTable.AUTO_RESIZE_OFF` and table width is smaller than scroll pane (was not
updated when table width changed and was painted on wrong side in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ public void paintBorder( Component c, Graphics g, int x, int y, int width, int h
Paint borderColor = (outlineColor != null) ? outlineColor : getBorderColor( c );
FlatUIUtils.paintOutlinedComponent( g2, x, y, width, height,
focusWidth, 1, focusInnerWidth, borderWidth, arc,
focusColor, borderColor, null );
focusColor, borderColor, null, c instanceof JScrollPane );
} finally {
g2.dispose();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ public static void paintThumb( Graphics g, JSlider slider, Rectangle thumbRect,
Color thumbColor, Color thumbBorderColor, Color focusedColor, float thumbBorderWidth, int focusWidth )
{
double systemScaleFactor = UIScale.getSystemScaleFactor( (Graphics2D) g );
if( systemScaleFactor != 1 && systemScaleFactor != 2 ) {
if( systemScaleFactor != (int) systemScaleFactor ) {
// paint at scale 1x to avoid clipping on right and bottom edges at 125%, 150% or 175%
HiDPIUtils.paintAtScale1x( (Graphics2D) g, thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height,
(g2d, x2, y2, width2, height2, scaleFactor) -> {
Expand Down
37 changes: 32 additions & 5 deletions flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -601,28 +601,55 @@ public static void paintComponentBackground( Graphics2D g, int x, int y, int wid
public static void paintOutlinedComponent( Graphics2D g, int x, int y, int width, int height,
float focusWidth, float focusWidthFraction, float focusInnerWidth, float borderWidth, float arc,
Paint focusColor, Paint borderColor, Paint background )
{
paintOutlinedComponent( g, x, y, width, height, focusWidth, focusWidthFraction, focusInnerWidth,
borderWidth, arc, focusColor, borderColor, background, false );
}

static void paintOutlinedComponent( Graphics2D g, int x, int y, int width, int height,
float focusWidth, float focusWidthFraction, float focusInnerWidth, float borderWidth, float arc,
Paint focusColor, Paint borderColor, Paint background, boolean scrollPane )
{
double systemScaleFactor = UIScale.getSystemScaleFactor( g );
if( systemScaleFactor != 1 && systemScaleFactor != 2 ) {
if( (int) systemScaleFactor != systemScaleFactor ) {
// paint at scale 1x to avoid clipping on right and bottom edges at 125%, 150% or 175%
HiDPIUtils.paintAtScale1x( g, x, y, width, height,
(g2d, x2, y2, width2, height2, scaleFactor) -> {
paintOutlinedComponentImpl( g2d, x2, y2, width2, height2,
(float) (focusWidth * scaleFactor), focusWidthFraction, (float) (focusInnerWidth * scaleFactor),
(float) (borderWidth * scaleFactor), (float) (arc * scaleFactor),
focusColor, borderColor, background );
focusColor, borderColor, background, scrollPane, scaleFactor );
} );
return;
}

paintOutlinedComponentImpl( g, x, y, width, height, focusWidth, focusWidthFraction, focusInnerWidth,
borderWidth, arc, focusColor, borderColor, background );
borderWidth, arc, focusColor, borderColor, background, scrollPane, systemScaleFactor );
}

@SuppressWarnings( "SelfAssignment" ) // Error Prone
private static void paintOutlinedComponentImpl( Graphics2D g, int x, int y, int width, int height,
float focusWidth, float focusWidthFraction, float focusInnerWidth, float borderWidth, float arc,
Paint focusColor, Paint borderColor, Paint background )
Paint focusColor, Paint borderColor, Paint background, boolean scrollPane, double scaleFactor )
{
// Special handling for scrollpane and fractional scale factors (e.g. 1.25 - 1.75),
// where Swing scales one "logical" pixel (border insets) to either one or two physical pixels.
// Antialiasing is used to paint the border, which usually needs two physical pixels
// at small scale factors. 1px for the solid border and another 1px for antialiasing.
// But scrollpane view is painted over the border, which results in a painted border
// that is 1px thick at some sides and 2px thick at other sides.
if( scrollPane && scaleFactor != (int) scaleFactor ) {
if( focusWidth > 0 ) {
// reduce outer border thickness (focusWidth) so that inner side of
// component border (focusWidth + borderWidth) is at a full pixel
int totalWidth = (int) (focusWidth + borderWidth);
focusWidth = totalWidth - borderWidth;
} else {// if( scaleFactor > 1 && scaleFactor < 2 ) {
// reduce component border thickness (borderWidth) to full pixels
borderWidth = (int) borderWidth;
}
}

// outside bounds of the border and the background
float x1 = x + focusWidth;
float y1 = y + focusWidth;
Expand Down Expand Up @@ -780,7 +807,7 @@ public static void paintSelection( Graphics2D g, int x, int y, int width, int he

if( arcTopLeft > 0 || arcTopRight > 0 || arcBottomLeft > 0 || arcBottomRight > 0 ) {
double systemScaleFactor = UIScale.getSystemScaleFactor( g );
if( systemScaleFactor != 1 && systemScaleFactor != 2 ) {
if( systemScaleFactor != (int) systemScaleFactor ) {
// paint at scale 1x to avoid clipping on right and bottom edges at 125%, 150% or 175%
HiDPIUtils.paintAtScale1x( g, x, y, width, height,
(g2d, x2, y2, width2, height2, scaleFactor) -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,15 @@ public static void main( String[] args ) {

FlatPaintingHiDPITest() {
initComponents();
reset();
sliderChanged();
}

@Override
public void addNotify() {
super.addNotify();
reset();
}

private void sliderChanged() {
painter.originX = originXSlider.getValue();
painter.originY = originYSlider.getValue();
Expand Down Expand Up @@ -212,7 +217,7 @@ private void initComponents() {
scaleXSlider.setPaintTicks(true);
scaleXSlider.setMajorTickSpacing(50);
scaleXSlider.setSnapToTicks(true);
scaleXSlider.setMinorTickSpacing(10);
scaleXSlider.setMinorTickSpacing(5);
scaleXSlider.setMinimum(-100);
scaleXSlider.addChangeListener(e -> sliderChanged());
add(scaleXSlider, "cell 1 4");
Expand All @@ -228,7 +233,7 @@ private void initComponents() {
scaleYSlider.setPaintLabels(true);
scaleYSlider.setMajorTickSpacing(50);
scaleYSlider.setSnapToTicks(true);
scaleYSlider.setMinorTickSpacing(10);
scaleYSlider.setMinorTickSpacing(5);
scaleYSlider.setMinimum(-100);
scaleYSlider.addChangeListener(e -> sliderChanged());
add(scaleYSlider, "cell 1 5");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
JFDML JFormDesigner: "8.0.0.0.122" Java: "17.0.2" encoding: "UTF-8"
JFDML JFormDesigner: "8.2.3.0.386" Java: "21" encoding: "UTF-8"

new FormModel {
contentType: "form/swing"
Expand Down Expand Up @@ -108,7 +108,7 @@ new FormModel {
"paintTicks": true
"majorTickSpacing": 50
"snapToTicks": true
"minorTickSpacing": 10
"minorTickSpacing": 5
"minimum": -100
auxiliary() {
"JavaCodeGenerator.variableLocal": false
Expand All @@ -131,7 +131,7 @@ new FormModel {
"paintLabels": true
"majorTickSpacing": 50
"snapToTicks": true
"minorTickSpacing": 10
"minorTickSpacing": 5
"minimum": -100
auxiliary() {
"JavaCodeGenerator.variableLocal": false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,19 @@
package com.formdev.flatlaf.testing;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.Collectors;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.MatteBorder;
import javax.swing.table.AbstractTableModel;
import javax.swing.tree.*;
Expand Down Expand Up @@ -227,6 +232,38 @@ private void viewportBorderChanged() {
}
}

private void emptyViewportChanged() {
boolean empty = emptyViewportCheckBox.isSelected();
for( JScrollPane scrollPane : allJScrollPanes ) {
JViewport viewport = scrollPane.getViewport();
Component view = viewport.getView();
if( empty ) {
scrollPane.putClientProperty( getClass().getName(), view );
JComponent emptyView = new JComponent() {
};
emptyView.setBorder( new EmptyViewBorder() );
emptyView.setFocusable( true );
emptyView.addMouseListener( new MouseAdapter() {
@Override
public void mousePressed( MouseEvent e ) {
emptyView.requestFocusInWindow();
}
} );
viewport.setView( emptyView );
} else {
Object oldView = scrollPane.getClientProperty( getClass().getName() );
scrollPane.putClientProperty( getClass().getName(), null );
if( oldView instanceof Component )
viewport.setView( (Component) oldView );
else
viewport.setView( null );
}
viewport.setOpaque( !empty );
scrollPane.revalidate();
scrollPane.repaint();
}
}

private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
splitPane2 = new JSplitPane();
Expand Down Expand Up @@ -265,6 +302,7 @@ private void initComponents() {
viewportBorderCheckBox = new JCheckBox();
rowHeaderCheckBox = new JCheckBox();
verticalScrollBarCheckBox = new JCheckBox();
emptyViewportCheckBox = new JCheckBox();

//======== this ========
setLayout(new MigLayout(
Expand Down Expand Up @@ -420,6 +458,7 @@ private void initComponents() {
"[]",
// rows
"[]" +
"[]" +
"[]"));

//---- arcLabel ----
Expand Down Expand Up @@ -468,6 +507,11 @@ private void initComponents() {
verticalScrollBarCheckBox.setSelected(true);
verticalScrollBarCheckBox.addActionListener(e -> verticalScrollBarChanged());
panel3.add(verticalScrollBarCheckBox, "cell 4 1");

//---- emptyViewportCheckBox ----
emptyViewportCheckBox.setText("Empty viewport");
emptyViewportCheckBox.addActionListener(e -> emptyViewportChanged());
panel3.add(emptyViewportCheckBox, "cell 2 2");
}
add(panel3, "cell 0 1");
// JFormDesigner - End of component initialization //GEN-END:initComponents
Expand Down Expand Up @@ -509,6 +553,7 @@ private void initComponents() {
private JCheckBox viewportBorderCheckBox;
private JCheckBox rowHeaderCheckBox;
private JCheckBox verticalScrollBarCheckBox;
private JCheckBox emptyViewportCheckBox;
// JFormDesigner - End of variables declaration //GEN-END:variables

//---- class Corner -------------------------------------------------------
Expand All @@ -525,4 +570,29 @@ public void setBackground( Color bg ) {
// do not change background when checkbox "explicit colors" is selected
}
}

//---- class EmptyViewBorder ----------------------------------------------

private static class EmptyViewBorder
extends EmptyBorder
{
public EmptyViewBorder() {
super( 0, 0, 0, 0 );
}

@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
g.setColor( Color.red );
int x2 = x + width - 1;
int y2 = y + height - 1;
for( int px = x; px <= x2; px += 4 ) {
g.fillRect( px, y, 1, 1 );
g.fillRect( px, y2, 1, 1 );
}
for( int py = y; py <= y2; py += 4 ) {
g.fillRect( x, py, 1, 1 );
g.fillRect( x2, py, 1, 1 );
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
JFDML JFormDesigner: "8.1.1.0.298" Java: "19.0.2" encoding: "UTF-8"
JFDML JFormDesigner: "8.2.3.0.386" Java: "21" encoding: "UTF-8"

new FormModel {
contentType: "form/swing"
Expand Down Expand Up @@ -169,7 +169,7 @@ new FormModel {
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "hidemode 3"
"$columnConstraints": "[fill][grow,fill]para[][][]"
"$rowConstraints": "[][]"
"$rowConstraints": "[][][]"
} ) {
name: "panel3"
add( new FormComponent( "javax.swing.JLabel" ) {
Expand Down Expand Up @@ -238,6 +238,13 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 4 1"
} )
add( new FormComponent( "javax.swing.JCheckBox" ) {
name: "emptyViewportCheckBox"
"text": "Empty viewport"
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "emptyViewportChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 2 2"
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 1"
} )
Expand Down

0 comments on commit 0c0d4bf

Please sign in to comment.