mirror of
https://github.com/JFormDesigner/FlatLaf.git
synced 2026-02-11 14:37:13 -06:00
ScrollBar: fixed temporary painting at wrong location during smooth scrolling when using mouse-wheel or scroll bar
(still occurs when scrolling by moving selection via keyboard) many thanks to @Chrriis for the idea to temporary disable blitting mode on viewport
This commit is contained in:
@@ -34,6 +34,7 @@ import javax.swing.JButton;
|
|||||||
import javax.swing.JComponent;
|
import javax.swing.JComponent;
|
||||||
import javax.swing.JScrollBar;
|
import javax.swing.JScrollBar;
|
||||||
import javax.swing.JScrollPane;
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.JViewport;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import javax.swing.UIManager;
|
import javax.swing.UIManager;
|
||||||
import javax.swing.plaf.ComponentUI;
|
import javax.swing.plaf.ComponentUI;
|
||||||
@@ -480,12 +481,14 @@ public class FlatScrollBarUI
|
|||||||
// remember current scrollbar value so that we can start scroll animation from there
|
// remember current scrollbar value so that we can start scroll animation from there
|
||||||
int oldValue = scrollbar.getValue();
|
int oldValue = scrollbar.getValue();
|
||||||
|
|
||||||
// if invoked while animation is running, calculation of new value
|
runWithoutBlitting( scrollbar.getParent(), () ->{
|
||||||
// should start at the previous target value
|
// if invoked while animation is running, calculation of new value
|
||||||
if( targetValue != Integer.MIN_VALUE )
|
// should start at the previous target value
|
||||||
scrollbar.setValue( targetValue );
|
if( targetValue != Integer.MIN_VALUE )
|
||||||
|
scrollbar.setValue( targetValue );
|
||||||
|
|
||||||
r.run();
|
r.run();
|
||||||
|
} );
|
||||||
|
|
||||||
// do not use animation if started dragging thumb
|
// do not use animation if started dragging thumb
|
||||||
if( isDragging ) {
|
if( isDragging ) {
|
||||||
@@ -507,6 +510,26 @@ public class FlatScrollBarUI
|
|||||||
inRunAndSetValueAnimated = false;
|
inRunAndSetValueAnimated = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void runWithoutBlitting( Container scrollPane, Runnable r ) {
|
||||||
|
// prevent the viewport to immediately repaint using blitting
|
||||||
|
JViewport viewport = null;
|
||||||
|
int oldScrollMode = 0;
|
||||||
|
if( scrollPane instanceof JScrollPane ) {
|
||||||
|
viewport = ((JScrollPane) scrollPane).getViewport();
|
||||||
|
if( viewport != null ) {
|
||||||
|
oldScrollMode = viewport.getScrollMode();
|
||||||
|
viewport.setScrollMode( JViewport.BACKINGSTORE_SCROLL_MODE );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
r.run();
|
||||||
|
} finally {
|
||||||
|
if( viewport != null )
|
||||||
|
viewport.setScrollMode( oldScrollMode );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean inRunAndSetValueAnimated;
|
private boolean inRunAndSetValueAnimated;
|
||||||
private Animator animator;
|
private Animator animator;
|
||||||
private int startValue = Integer.MIN_VALUE;
|
private int startValue = Integer.MIN_VALUE;
|
||||||
|
|||||||
@@ -455,6 +455,8 @@ public class FlatScrollPaneUI
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void syncScrollPaneWithViewport() {
|
protected void syncScrollPaneWithViewport() {
|
||||||
|
// if the viewport has been scrolled by using JComponent.scrollRectToVisible()
|
||||||
|
// (e.g. by moving selection), then it is necessary to update the scroll bar values
|
||||||
if( isSmoothScrollingEnabled() ) {
|
if( isSmoothScrollingEnabled() ) {
|
||||||
runAndSyncScrollBarValueAnimated( scrollpane.getVerticalScrollBar(), 0, () -> {
|
runAndSyncScrollBarValueAnimated( scrollpane.getVerticalScrollBar(), 0, () -> {
|
||||||
runAndSyncScrollBarValueAnimated( scrollpane.getHorizontalScrollBar(), 1, () -> {
|
runAndSyncScrollBarValueAnimated( scrollpane.getHorizontalScrollBar(), 1, () -> {
|
||||||
|
|||||||
@@ -78,26 +78,26 @@ public class FlatSmoothScrollingTest
|
|||||||
editorPaneLabel.setIcon( new ColorIcon( Color.orange.darker() ) );
|
editorPaneLabel.setIcon( new ColorIcon( Color.orange.darker() ) );
|
||||||
customLabel.setIcon( new ColorIcon( Color.pink ) );
|
customLabel.setIcon( new ColorIcon( Color.pink ) );
|
||||||
|
|
||||||
listScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "list vert", Color.red.darker() ) );
|
listScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( listScrollPane, true, "list vert", Color.red.darker() ) );
|
||||||
listScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "list horz", Color.red ) );
|
listScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( listScrollPane, false, "list horz", Color.red ) );
|
||||||
|
|
||||||
treeScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "tree vert", Color.blue.darker() ) );
|
treeScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( treeScrollPane, true, "tree vert", Color.blue.darker() ) );
|
||||||
treeScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "tree horz", Color.blue ) );
|
treeScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( treeScrollPane, false, "tree horz", Color.blue ) );
|
||||||
|
|
||||||
tableScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "table vert", Color.green.darker() ) );
|
tableScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( tableScrollPane, true, "table vert", Color.green.darker() ) );
|
||||||
tableScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "table horz", Color.green ) );
|
tableScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( tableScrollPane, false, "table horz", Color.green ) );
|
||||||
|
|
||||||
textAreaScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "textArea vert", Color.magenta.darker() ) );
|
textAreaScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( textAreaScrollPane, true, "textArea vert", Color.magenta.darker() ) );
|
||||||
textAreaScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "textArea horz", Color.magenta ) );
|
textAreaScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( textAreaScrollPane, false, "textArea horz", Color.magenta ) );
|
||||||
|
|
||||||
textPaneScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "textPane vert", Color.cyan.darker() ) );
|
textPaneScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( textPaneScrollPane, true, "textPane vert", Color.cyan.darker() ) );
|
||||||
textPaneScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "textPane horz", Color.cyan ) );
|
textPaneScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( textPaneScrollPane, false, "textPane horz", Color.cyan ) );
|
||||||
|
|
||||||
editorPaneScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "editorPane vert", Color.orange.darker() ) );
|
editorPaneScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( editorPaneScrollPane, true, "editorPane vert", Color.orange.darker() ) );
|
||||||
editorPaneScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "editorPane horz", Color.orange ) );
|
editorPaneScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( editorPaneScrollPane, false, "editorPane horz", Color.orange ) );
|
||||||
|
|
||||||
customScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "custom vert", Color.pink ) );
|
customScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( customScrollPane, true, "custom vert", Color.pink ) );
|
||||||
customScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "custom horz", Color.pink.darker() ) );
|
customScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( customScrollPane, false, "custom horz", Color.pink.darker() ) );
|
||||||
|
|
||||||
ArrayList<String> items = new ArrayList<>();
|
ArrayList<String> items = new ArrayList<>();
|
||||||
for( char ch = '0'; ch < 'z'; ch++ ) {
|
for( char ch = '0'; ch < 'z'; ch++ ) {
|
||||||
@@ -151,6 +151,13 @@ public class FlatSmoothScrollingTest
|
|||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
// select some rows to better see smooth scrolling issues
|
||||||
|
for( int i = 5; i < items.size(); i += 10 ) {
|
||||||
|
list.addSelectionInterval( i, i );
|
||||||
|
tree.addSelectionInterval( i, i );
|
||||||
|
table.addRowSelectionInterval( i, i );
|
||||||
|
}
|
||||||
|
|
||||||
// text components
|
// text components
|
||||||
String text = items.stream().collect( Collectors.joining( "\n" ) );
|
String text = items.stream().collect( Collectors.joining( "\n" ) );
|
||||||
textArea.setText( text );
|
textArea.setText( text );
|
||||||
@@ -207,7 +214,7 @@ public class FlatSmoothScrollingTest
|
|||||||
list = new JList<>();
|
list = new JList<>();
|
||||||
treeScrollPane = new FlatSmoothScrollingTest.DebugScrollPane();
|
treeScrollPane = new FlatSmoothScrollingTest.DebugScrollPane();
|
||||||
tree = new JTree();
|
tree = new JTree();
|
||||||
tableScrollPane = new JScrollPane();
|
tableScrollPane = new FlatSmoothScrollingTest.DebugScrollPane();
|
||||||
table = new JTable();
|
table = new JTable();
|
||||||
textAreaLabel = new JLabel();
|
textAreaLabel = new JLabel();
|
||||||
textPaneLabel = new JLabel();
|
textPaneLabel = new JLabel();
|
||||||
@@ -219,7 +226,7 @@ public class FlatSmoothScrollingTest
|
|||||||
textPane = new JTextPane();
|
textPane = new JTextPane();
|
||||||
editorPaneScrollPane = new FlatSmoothScrollingTest.DebugScrollPane();
|
editorPaneScrollPane = new FlatSmoothScrollingTest.DebugScrollPane();
|
||||||
editorPane = new JEditorPane();
|
editorPane = new JEditorPane();
|
||||||
customScrollPane = new JScrollPane();
|
customScrollPane = new FlatSmoothScrollingTest.DebugScrollPane();
|
||||||
button1 = new JButton();
|
button1 = new JButton();
|
||||||
scrollPane1 = new JScrollPane();
|
scrollPane1 = new JScrollPane();
|
||||||
lineChartPanel = new FlatSmoothScrollingTest.LineChartPanel();
|
lineChartPanel = new FlatSmoothScrollingTest.LineChartPanel();
|
||||||
@@ -382,7 +389,7 @@ public class FlatSmoothScrollingTest
|
|||||||
private JList<String> list;
|
private JList<String> list;
|
||||||
private FlatSmoothScrollingTest.DebugScrollPane treeScrollPane;
|
private FlatSmoothScrollingTest.DebugScrollPane treeScrollPane;
|
||||||
private JTree tree;
|
private JTree tree;
|
||||||
private JScrollPane tableScrollPane;
|
private FlatSmoothScrollingTest.DebugScrollPane tableScrollPane;
|
||||||
private JTable table;
|
private JTable table;
|
||||||
private JLabel textAreaLabel;
|
private JLabel textAreaLabel;
|
||||||
private JLabel textPaneLabel;
|
private JLabel textPaneLabel;
|
||||||
@@ -394,7 +401,7 @@ public class FlatSmoothScrollingTest
|
|||||||
private JTextPane textPane;
|
private JTextPane textPane;
|
||||||
private FlatSmoothScrollingTest.DebugScrollPane editorPaneScrollPane;
|
private FlatSmoothScrollingTest.DebugScrollPane editorPaneScrollPane;
|
||||||
private JEditorPane editorPane;
|
private JEditorPane editorPane;
|
||||||
private JScrollPane customScrollPane;
|
private FlatSmoothScrollingTest.DebugScrollPane customScrollPane;
|
||||||
private JButton button1;
|
private JButton button1;
|
||||||
private JScrollPane scrollPane1;
|
private JScrollPane scrollPane1;
|
||||||
private FlatSmoothScrollingTest.LineChartPanel lineChartPanel;
|
private FlatSmoothScrollingTest.LineChartPanel lineChartPanel;
|
||||||
@@ -413,32 +420,43 @@ public class FlatSmoothScrollingTest
|
|||||||
private int count;
|
private int count;
|
||||||
private long lastTime;
|
private long lastTime;
|
||||||
|
|
||||||
ScrollBarChangeHandler( String name, Color chartColor ) {
|
ScrollBarChangeHandler( DebugScrollPane scrollPane, boolean vertical, String name, Color chartColor ) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.chartColor = chartColor;
|
this.chartColor = chartColor;
|
||||||
|
|
||||||
|
// add change listener to viewport that is invoked from JViewport.setViewPosition()
|
||||||
|
scrollPane.getViewport().addChangeListener( e -> {
|
||||||
|
// add dot to chart if blit scroll mode is disabled
|
||||||
|
if( vertical == scrollPane.lastScrollingWasVertical &&
|
||||||
|
scrollPane.getViewport().getScrollMode() != JViewport.BLIT_SCROLL_MODE )
|
||||||
|
{
|
||||||
|
JScrollBar sb = vertical ? scrollPane.getVerticalScrollBar() : scrollPane.getHorizontalScrollBar();
|
||||||
|
lineChartPanel.addValue( getChartValue( sb.getModel() ), true, chartColor );
|
||||||
|
}
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stateChanged( ChangeEvent e ) {
|
public void stateChanged( ChangeEvent e ) {
|
||||||
DefaultBoundedRangeModel m = (DefaultBoundedRangeModel) e.getSource();
|
DefaultBoundedRangeModel m = (DefaultBoundedRangeModel) e.getSource();
|
||||||
int value = m.getValue();
|
|
||||||
boolean valueIsAdjusting = m.getValueIsAdjusting();
|
|
||||||
|
|
||||||
if( chartColor != null ) {
|
lineChartPanel.addValue( getChartValue( m ), chartColor );
|
||||||
double chartValue = (double) (value - m.getMinimum()) / (double) (m.getMaximum() - m.getExtent());
|
|
||||||
lineChartPanel.addValue( chartValue, chartColor );
|
|
||||||
}
|
|
||||||
|
|
||||||
long t = System.nanoTime() / 1000000;
|
long t = System.nanoTime() / 1000000;
|
||||||
|
|
||||||
System.out.printf( "%s (%d): %4d %3d ms %b%n",
|
System.out.printf( "%s (%d): %4d %3d ms %b%n",
|
||||||
name, ++count,
|
name, ++count,
|
||||||
value,
|
m.getValue(),
|
||||||
t - lastTime,
|
t - lastTime,
|
||||||
valueIsAdjusting );
|
m.getValueIsAdjusting() );
|
||||||
|
|
||||||
lastTime = t;
|
lastTime = t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private double getChartValue( BoundedRangeModel m ) {
|
||||||
|
int value = m.getValue();
|
||||||
|
return (double) (value - m.getMinimum()) / (double) (m.getMaximum() - m.getExtent());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//---- class DebugViewport ------------------------------------------------
|
//---- class DebugViewport ------------------------------------------------
|
||||||
@@ -446,6 +464,8 @@ public class FlatSmoothScrollingTest
|
|||||||
private static class DebugScrollPane
|
private static class DebugScrollPane
|
||||||
extends JScrollPane
|
extends JScrollPane
|
||||||
{
|
{
|
||||||
|
boolean lastScrollingWasVertical;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected JViewport createViewport() {
|
protected JViewport createViewport() {
|
||||||
return new JViewport() {
|
return new JViewport() {
|
||||||
@@ -455,6 +475,23 @@ public class FlatSmoothScrollingTest
|
|||||||
// System.out.println( " viewPosition " + viewPosition.x + "," + viewPosition.y );
|
// System.out.println( " viewPosition " + viewPosition.x + "," + viewPosition.y );
|
||||||
return viewPosition;
|
return viewPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setViewPosition( Point p ) {
|
||||||
|
// remember whether scrolling vertically or horizontally
|
||||||
|
Component view = getView();
|
||||||
|
if( view != null ) {
|
||||||
|
int oldY = (view instanceof JComponent)
|
||||||
|
? ((JComponent) view).getY()
|
||||||
|
: view.getBounds().y;
|
||||||
|
|
||||||
|
int newY = -p.y;
|
||||||
|
lastScrollingWasVertical = (oldY != newY);
|
||||||
|
} else
|
||||||
|
lastScrollingWasVertical = true;
|
||||||
|
|
||||||
|
super.setViewPosition( p );
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
JFDML JFormDesigner: "7.0.2.0.298" Java: "14" encoding: "UTF-8"
|
JFDML JFormDesigner: "8.1.0.0.283" Java: "19.0.2" encoding: "UTF-8"
|
||||||
|
|
||||||
new FormModel {
|
new FormModel {
|
||||||
contentType: "form/swing"
|
contentType: "form/swing"
|
||||||
@@ -75,7 +75,7 @@ new FormModel {
|
|||||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||||
"value": "cell 1 2"
|
"value": "cell 1 2"
|
||||||
} )
|
} )
|
||||||
add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
|
add( new FormContainer( "com.formdev.flatlaf.testing.FlatSmoothScrollingTest$DebugScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
|
||||||
name: "tableScrollPane"
|
name: "tableScrollPane"
|
||||||
add( new FormComponent( "javax.swing.JTable" ) {
|
add( new FormComponent( "javax.swing.JTable" ) {
|
||||||
name: "table"
|
name: "table"
|
||||||
@@ -134,7 +134,7 @@ new FormModel {
|
|||||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||||
"value": "cell 2 4"
|
"value": "cell 2 4"
|
||||||
} )
|
} )
|
||||||
add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
|
add( new FormContainer( "com.formdev.flatlaf.testing.FlatSmoothScrollingTest$DebugScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
|
||||||
name: "customScrollPane"
|
name: "customScrollPane"
|
||||||
add( new FormComponent( "javax.swing.JButton" ) {
|
add( new FormComponent( "javax.swing.JButton" ) {
|
||||||
name: "button1"
|
name: "button1"
|
||||||
|
|||||||
Reference in New Issue
Block a user