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:
Karl Tauber
2023-06-04 19:40:18 +02:00
parent 29f6c5fae9
commit cf70cfb50c
4 changed files with 97 additions and 35 deletions

View File

@@ -34,6 +34,7 @@ import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
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
int oldValue = scrollbar.getValue();
// if invoked while animation is running, calculation of new value
// should start at the previous target value
if( targetValue != Integer.MIN_VALUE )
scrollbar.setValue( targetValue );
runWithoutBlitting( scrollbar.getParent(), () ->{
// if invoked while animation is running, calculation of new value
// should start at the previous target value
if( targetValue != Integer.MIN_VALUE )
scrollbar.setValue( targetValue );
r.run();
r.run();
} );
// do not use animation if started dragging thumb
if( isDragging ) {
@@ -507,6 +510,26 @@ public class FlatScrollBarUI
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 Animator animator;
private int startValue = Integer.MIN_VALUE;

View File

@@ -455,6 +455,8 @@ public class FlatScrollPaneUI
@Override
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() ) {
runAndSyncScrollBarValueAnimated( scrollpane.getVerticalScrollBar(), 0, () -> {
runAndSyncScrollBarValueAnimated( scrollpane.getHorizontalScrollBar(), 1, () -> {

View File

@@ -78,26 +78,26 @@ public class FlatSmoothScrollingTest
editorPaneLabel.setIcon( new ColorIcon( Color.orange.darker() ) );
customLabel.setIcon( new ColorIcon( Color.pink ) );
listScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "list vert", Color.red.darker() ) );
listScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "list horz", Color.red ) );
listScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( listScrollPane, true, "list vert", Color.red.darker() ) );
listScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( listScrollPane, false, "list horz", Color.red ) );
treeScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "tree vert", Color.blue.darker() ) );
treeScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "tree horz", Color.blue ) );
treeScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( treeScrollPane, true, "tree vert", Color.blue.darker() ) );
treeScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( treeScrollPane, false, "tree horz", Color.blue ) );
tableScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "table vert", Color.green.darker() ) );
tableScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "table horz", Color.green ) );
tableScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( tableScrollPane, true, "table vert", Color.green.darker() ) );
tableScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( tableScrollPane, false, "table horz", Color.green ) );
textAreaScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "textArea vert", Color.magenta.darker() ) );
textAreaScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "textArea horz", Color.magenta ) );
textAreaScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( textAreaScrollPane, true, "textArea vert", Color.magenta.darker() ) );
textAreaScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( textAreaScrollPane, false, "textArea horz", Color.magenta ) );
textPaneScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "textPane vert", Color.cyan.darker() ) );
textPaneScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "textPane horz", Color.cyan ) );
textPaneScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( textPaneScrollPane, true, "textPane vert", Color.cyan.darker() ) );
textPaneScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( textPaneScrollPane, false, "textPane horz", Color.cyan ) );
editorPaneScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "editorPane vert", Color.orange.darker() ) );
editorPaneScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "editorPane horz", Color.orange ) );
editorPaneScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( editorPaneScrollPane, true, "editorPane vert", Color.orange.darker() ) );
editorPaneScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( editorPaneScrollPane, false, "editorPane horz", Color.orange ) );
customScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "custom vert", Color.pink ) );
customScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( "custom horz", Color.pink.darker() ) );
customScrollPane.getVerticalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( customScrollPane, true, "custom vert", Color.pink ) );
customScrollPane.getHorizontalScrollBar().getModel().addChangeListener( new ScrollBarChangeHandler( customScrollPane, false, "custom horz", Color.pink.darker() ) );
ArrayList<String> items = new ArrayList<>();
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
String text = items.stream().collect( Collectors.joining( "\n" ) );
textArea.setText( text );
@@ -207,7 +214,7 @@ public class FlatSmoothScrollingTest
list = new JList<>();
treeScrollPane = new FlatSmoothScrollingTest.DebugScrollPane();
tree = new JTree();
tableScrollPane = new JScrollPane();
tableScrollPane = new FlatSmoothScrollingTest.DebugScrollPane();
table = new JTable();
textAreaLabel = new JLabel();
textPaneLabel = new JLabel();
@@ -219,7 +226,7 @@ public class FlatSmoothScrollingTest
textPane = new JTextPane();
editorPaneScrollPane = new FlatSmoothScrollingTest.DebugScrollPane();
editorPane = new JEditorPane();
customScrollPane = new JScrollPane();
customScrollPane = new FlatSmoothScrollingTest.DebugScrollPane();
button1 = new JButton();
scrollPane1 = new JScrollPane();
lineChartPanel = new FlatSmoothScrollingTest.LineChartPanel();
@@ -382,7 +389,7 @@ public class FlatSmoothScrollingTest
private JList<String> list;
private FlatSmoothScrollingTest.DebugScrollPane treeScrollPane;
private JTree tree;
private JScrollPane tableScrollPane;
private FlatSmoothScrollingTest.DebugScrollPane tableScrollPane;
private JTable table;
private JLabel textAreaLabel;
private JLabel textPaneLabel;
@@ -394,7 +401,7 @@ public class FlatSmoothScrollingTest
private JTextPane textPane;
private FlatSmoothScrollingTest.DebugScrollPane editorPaneScrollPane;
private JEditorPane editorPane;
private JScrollPane customScrollPane;
private FlatSmoothScrollingTest.DebugScrollPane customScrollPane;
private JButton button1;
private JScrollPane scrollPane1;
private FlatSmoothScrollingTest.LineChartPanel lineChartPanel;
@@ -413,32 +420,43 @@ public class FlatSmoothScrollingTest
private int count;
private long lastTime;
ScrollBarChangeHandler( String name, Color chartColor ) {
ScrollBarChangeHandler( DebugScrollPane scrollPane, boolean vertical, String name, Color chartColor ) {
this.name = name;
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
public void stateChanged( ChangeEvent e ) {
DefaultBoundedRangeModel m = (DefaultBoundedRangeModel) e.getSource();
int value = m.getValue();
boolean valueIsAdjusting = m.getValueIsAdjusting();
if( chartColor != null ) {
double chartValue = (double) (value - m.getMinimum()) / (double) (m.getMaximum() - m.getExtent());
lineChartPanel.addValue( chartValue, chartColor );
}
lineChartPanel.addValue( getChartValue( m ), chartColor );
long t = System.nanoTime() / 1000000;
System.out.printf( "%s (%d): %4d %3d ms %b%n",
name, ++count,
value,
m.getValue(),
t - lastTime,
valueIsAdjusting );
m.getValueIsAdjusting() );
lastTime = t;
}
private double getChartValue( BoundedRangeModel m ) {
int value = m.getValue();
return (double) (value - m.getMinimum()) / (double) (m.getMaximum() - m.getExtent());
}
}
//---- class DebugViewport ------------------------------------------------
@@ -446,6 +464,8 @@ public class FlatSmoothScrollingTest
private static class DebugScrollPane
extends JScrollPane
{
boolean lastScrollingWasVertical;
@Override
protected JViewport createViewport() {
return new JViewport() {
@@ -455,6 +475,23 @@ public class FlatSmoothScrollingTest
// System.out.println( " viewPosition " + viewPosition.x + "," + viewPosition.y );
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 );
}
};
}
}

View File

@@ -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 {
contentType: "form/swing"
@@ -75,7 +75,7 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"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"
add( new FormComponent( "javax.swing.JTable" ) {
name: "table"
@@ -134,7 +134,7 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"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"
add( new FormComponent( "javax.swing.JButton" ) {
name: "button1"