diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollBarUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollBarUI.java index a0b3c47d..732e0fa3 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollBarUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollBarUI.java @@ -473,6 +473,8 @@ public class FlatScrollBarUI if( animator != null ) animator.cancel(); + scrollbar.setValueIsAdjusting( true ); + // if invoked while animation is running, calculation of new value // should start at the previous target value if( targetValue != Integer.MIN_VALUE ) @@ -487,7 +489,8 @@ public class FlatScrollBarUI scrollbar.setValue( oldValue ); setValueAnimated( newValue ); - } + } else + scrollbar.setValueIsAdjusting( false ); inRunAndSetValueAnimated = false; } @@ -498,6 +501,8 @@ public class FlatScrollBarUI private int delta; public void setValueAnimated( int value ) { + scrollbar.setValueIsAdjusting( true ); + // create animator if( animator == null ) { int duration = FlatUIUtils.getUIInt( "ScrollPane.smoothScrolling.duration", 200 ); @@ -510,9 +515,15 @@ public class FlatScrollBarUI return; } + // re-enable valueIsAdjusting if disabled while animation is running + // (e.g. in mouse released listener) + if( !scrollbar.getValueIsAdjusting() ) + scrollbar.setValueIsAdjusting( true ); + scrollbar.setValue( targetValue - delta + Math.round( delta * fraction ) ); }, () -> { targetValue = Integer.MIN_VALUE; + scrollbar.setValueIsAdjusting( false ); }); animator.setResolution( resolution ); diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSmoothScrollingTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSmoothScrollingTest.java new file mode 100644 index 00000000..2c8e915c --- /dev/null +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSmoothScrollingTest.java @@ -0,0 +1,144 @@ +/* + * Copyright 2020 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.event.AdjustmentEvent; +import java.awt.event.AdjustmentListener; +import java.util.ArrayList; +import java.util.Arrays; +import javax.swing.*; +import net.miginfocom.swing.*; + +/** + * @author Karl Tauber + */ +public class FlatSmoothScrollingTest + extends FlatTestPanel +{ + public static void main( String[] args ) { + SwingUtilities.invokeLater( () -> { + FlatTestFrame frame = FlatTestFrame.create( args, "FlatSmoothScrollingTest" ); + UIManager.put( "ScrollBar.showButtons", true ); + frame.showFrame( FlatSmoothScrollingTest::new ); + } ); + } + + FlatSmoothScrollingTest() { + initComponents(); + + scrollPane1.getVerticalScrollBar().addAdjustmentListener( new AdjustmentHandler( "list vert" ) ); + scrollPane1.getHorizontalScrollBar().addAdjustmentListener( new AdjustmentHandler( "list horz" ) ); + + ArrayList items = new ArrayList<>(); + for( char ch = '0'; ch < 'z'; ch++ ) { + char[] chars = new char[ch - '0' + 1]; + Arrays.fill( chars, ch ); + items.add( new String( chars ) ); + } + + list1.setModel( new AbstractListModel() { + @Override + public int getSize() { + return items.size(); + } + + @Override + public String getElementAt( int index ) { + return items.get( index ); + } + } ); + } + + private void smoothScrollingChanged() { + UIManager.put( "ScrollPane.smoothScrolling", smoothScrollingCheckBox.isSelected() ); + } + + private void initComponents() { + // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents + smoothScrollingCheckBox = new JCheckBox(); + listLabel = new JLabel(); + scrollPane1 = new JScrollPane(); + list1 = new JList<>(); + + //======== this ======== + setLayout(new MigLayout( + "ltr,insets dialog,hidemode 3", + // columns + "[]" + + "[200]", + // rows + "[]" + + "[::200,grow,fill]")); + + //---- smoothScrollingCheckBox ---- + smoothScrollingCheckBox.setText("Smooth scrolling"); + smoothScrollingCheckBox.setSelected(true); + smoothScrollingCheckBox.addActionListener(e -> smoothScrollingChanged()); + add(smoothScrollingCheckBox, "cell 0 0 2 1,alignx left,growx 0"); + + //---- listLabel ---- + listLabel.setText("JList:"); + add(listLabel, "cell 0 1,aligny top,growy 0"); + + //======== scrollPane1 ======== + { + scrollPane1.setViewportView(list1); + } + add(scrollPane1, "cell 1 1,growx"); + // JFormDesigner - End of component initialization //GEN-END:initComponents + } + + // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables + private JCheckBox smoothScrollingCheckBox; + private JLabel listLabel; + private JScrollPane scrollPane1; + private JList list1; + // JFormDesigner - End of variables declaration //GEN-END:variables + + //---- class AdjustmentHandler -------------------------------------------- + + private static class AdjustmentHandler + implements AdjustmentListener + { + private final String name; + private int count; + + AdjustmentHandler( String name ) { + this.name = name; + } + + @Override + public void adjustmentValueChanged( AdjustmentEvent e ) { + System.out.printf( "%s (%d): %s %3d %b%n", + name, ++count, + adjustmentType2Str( e.getAdjustmentType() ), + e.getValue(), + e.getValueIsAdjusting() ); + } + + private String adjustmentType2Str( int adjustmentType ) { + switch( adjustmentType ) { + case AdjustmentEvent.UNIT_INCREMENT: return "UNIT_INCREMENT"; + case AdjustmentEvent.UNIT_DECREMENT: return "UNIT_DECREMENT"; + case AdjustmentEvent.BLOCK_INCREMENT: return "BLOCK_INCREMENT"; + case AdjustmentEvent.BLOCK_DECREMENT: return "BLOCK_DECREMENT"; + case AdjustmentEvent.TRACK: return "TRACK"; + default: return "unknown type"; + } + } + } +} diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSmoothScrollingTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSmoothScrollingTest.jfd new file mode 100644 index 00000000..39231291 --- /dev/null +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSmoothScrollingTest.jfd @@ -0,0 +1,43 @@ +JFDML JFormDesigner: "7.0.2.0.298" Java: "14" encoding: "UTF-8" + +new FormModel { + contentType: "form/swing" + root: new FormRoot { + add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "ltr,insets dialog,hidemode 3" + "$columnConstraints": "[][200]" + "$rowConstraints": "[][::200,grow,fill]" + } ) { + name: "this" + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "smoothScrollingCheckBox" + "text": "Smooth scrolling" + "selected": true + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "smoothScrollingChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0 2 1,alignx left,growx 0" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "listLabel" + "text": "JList:" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1,aligny top,growy 0" + } ) + add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) { + name: "scrollPane1" + add( new FormComponent( "javax.swing.JList" ) { + name: "list1" + auxiliary() { + "JavaCodeGenerator.typeParameters": "String" + "JavaCodeGenerator.variableLocal": false + } + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 1,growx" + } ) + }, new FormLayoutConstraints( null ) { + "location": new java.awt.Point( 0, 0 ) + "size": new java.awt.Dimension( 790, 715 ) + } ) + } +}