mirror of
https://github.com/JFormDesigner/FlatLaf.git
synced 2025-12-06 22:10:54 +03:00
ScrollPane: fixed/improved border painting at 125% - 175% scaling to avoid different border thicknesses (issue #743)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -135,7 +135,7 @@ public class FlatBorder
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -422,7 +422,7 @@ debug*/
|
||||
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) -> {
|
||||
|
||||
@@ -601,28 +601,55 @@ public class FlatUIUtils
|
||||
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;
|
||||
@@ -780,7 +807,7 @@ public class FlatUIUtils
|
||||
|
||||
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) -> {
|
||||
|
||||
@@ -40,10 +40,15 @@ public class FlatPaintingHiDPITest
|
||||
|
||||
FlatPaintingHiDPITest() {
|
||||
initComponents();
|
||||
reset();
|
||||
sliderChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNotify() {
|
||||
super.addNotify();
|
||||
reset();
|
||||
}
|
||||
|
||||
private void sliderChanged() {
|
||||
painter.originX = originXSlider.getValue();
|
||||
painter.originY = originYSlider.getValue();
|
||||
@@ -212,7 +217,7 @@ public class FlatPaintingHiDPITest
|
||||
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");
|
||||
@@ -228,7 +233,7 @@ public class FlatPaintingHiDPITest
|
||||
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");
|
||||
|
||||
@@ -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"
|
||||
@@ -108,7 +108,7 @@ new FormModel {
|
||||
"paintTicks": true
|
||||
"majorTickSpacing": 50
|
||||
"snapToTicks": true
|
||||
"minorTickSpacing": 10
|
||||
"minorTickSpacing": 5
|
||||
"minimum": -100
|
||||
auxiliary() {
|
||||
"JavaCodeGenerator.variableLocal": false
|
||||
@@ -131,7 +131,7 @@ new FormModel {
|
||||
"paintLabels": true
|
||||
"majorTickSpacing": 50
|
||||
"snapToTicks": true
|
||||
"minorTickSpacing": 10
|
||||
"minorTickSpacing": 5
|
||||
"minimum": -100
|
||||
auxiliary() {
|
||||
"JavaCodeGenerator.variableLocal": false
|
||||
|
||||
@@ -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.*;
|
||||
@@ -227,6 +232,38 @@ public class FlatRoundedScrollPaneTest
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -265,6 +302,7 @@ public class FlatRoundedScrollPaneTest
|
||||
viewportBorderCheckBox = new JCheckBox();
|
||||
rowHeaderCheckBox = new JCheckBox();
|
||||
verticalScrollBarCheckBox = new JCheckBox();
|
||||
emptyViewportCheckBox = new JCheckBox();
|
||||
|
||||
//======== this ========
|
||||
setLayout(new MigLayout(
|
||||
@@ -420,6 +458,7 @@ public class FlatRoundedScrollPaneTest
|
||||
"[]",
|
||||
// rows
|
||||
"[]" +
|
||||
"[]" +
|
||||
"[]"));
|
||||
|
||||
//---- arcLabel ----
|
||||
@@ -468,6 +507,11 @@ public class FlatRoundedScrollPaneTest
|
||||
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
|
||||
@@ -509,6 +553,7 @@ public class FlatRoundedScrollPaneTest
|
||||
private JCheckBox viewportBorderCheckBox;
|
||||
private JCheckBox rowHeaderCheckBox;
|
||||
private JCheckBox verticalScrollBarCheckBox;
|
||||
private JCheckBox emptyViewportCheckBox;
|
||||
// JFormDesigner - End of variables declaration //GEN-END:variables
|
||||
|
||||
//---- class Corner -------------------------------------------------------
|
||||
@@ -525,4 +570,29 @@ public class FlatRoundedScrollPaneTest
|
||||
// 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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -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" ) {
|
||||
@@ -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"
|
||||
} )
|
||||
|
||||
Reference in New Issue
Block a user