From 357318802528a4d3a4b508eeec275ef2bd776a41 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Wed, 12 Aug 2020 16:13:09 +0200 Subject: [PATCH] ScrollBar: support smooth scrolling via keyboard --- .../formdev/flatlaf/ui/FlatScrollBarUI.java | 48 +++++++++++++------ .../formdev/flatlaf/ui/FlatScrollPaneUI.java | 43 +++++++++++++++++ 2 files changed, 77 insertions(+), 14 deletions(-) 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 87d43bc2..f9aef8e6 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 @@ -477,6 +477,7 @@ public class FlatScrollBarUI if( useValueIsAdjusting ) scrollbar.setValueIsAdjusting( true ); + // 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 @@ -488,31 +489,51 @@ public class FlatScrollBarUI // do not use animation if started dragging thumb if( isDragging ) { + // do not clear valueIsAdjusting here inRunAndSetValueAnimated = false; return; } int newValue = scrollbar.getValue(); if( newValue != oldValue ) { - scrollbar.setValue( oldValue ); - - setValueAnimated( newValue ); - } else if( useValueIsAdjusting ) - scrollbar.setValueIsAdjusting( false ); + // start scroll animation if value has changed + setValueAnimated( oldValue, newValue ); + } else { + // clear valueIsAdjusting if value has not changed + if( useValueIsAdjusting ) + scrollbar.setValueIsAdjusting( false ); + } inRunAndSetValueAnimated = false; } private boolean inRunAndSetValueAnimated; private Animator animator; + private int startValue = Integer.MIN_VALUE; private int targetValue = Integer.MIN_VALUE; - private int delta; private boolean useValueIsAdjusting = true; - public void setValueAnimated( int value ) { + public void setValueAnimated( int initialValue, int value ) { + // do some check if animation already running + if( animator != null && animator.isRunning() && targetValue != Integer.MIN_VALUE ) { + // ignore requests if animation still running and scroll direction is the same + // and new value is within currently running animation + // (this may occur when repeat-scrolling via keyboard) + if( value == targetValue || + (value > startValue && value < targetValue) || // scroll down/right + (value < startValue && value > targetValue) ) // scroll up/left + return; + } + if( useValueIsAdjusting ) scrollbar.setValueIsAdjusting( true ); + // set scrollbar value to initial value + scrollbar.setValue( initialValue ); + + startValue = initialValue; + targetValue = value; + // create animator if( animator == null ) { int duration = FlatUIUtils.getUIInt( "ScrollPane.smoothScrolling.duration", 200 ); @@ -521,7 +542,7 @@ public class FlatScrollBarUI animator = new Animator( duration, fraction -> { if( scrollbar == null || !scrollbar.isShowing() ) { - animator.cancel(); + animator.stop(); return; } @@ -530,10 +551,11 @@ public class FlatScrollBarUI if( useValueIsAdjusting && !scrollbar.getValueIsAdjusting() ) scrollbar.setValueIsAdjusting( true ); - scrollbar.setValue( targetValue - delta + Math.round( delta * fraction ) ); + scrollbar.setValue( startValue + Math.round( (targetValue - startValue) * fraction ) ); }, () -> { - targetValue = Integer.MIN_VALUE; - if( useValueIsAdjusting ) + startValue = targetValue = Integer.MIN_VALUE; + + if( useValueIsAdjusting && scrollbar != null ) scrollbar.setValueIsAdjusting( false ); }); @@ -543,9 +565,7 @@ public class FlatScrollBarUI : new CubicBezierEasing( 0.5f, 0.5f, 0.5f, 1 ) ); } - targetValue = value; - delta = targetValue - scrollbar.getValue(); - + // restart animator animator.cancel(); animator.start(); } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java index bcfb6efb..c1378162 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollPaneUI.java @@ -453,6 +453,49 @@ public class FlatScrollPaneUI return false; } + @Override + protected void syncScrollPaneWithViewport() { + if( isSmoothScrollingEnabled() ) { + runAndSyncScrollBarValueAnimated( scrollpane.getVerticalScrollBar(), 0, () -> { + runAndSyncScrollBarValueAnimated( scrollpane.getHorizontalScrollBar(), 1, () -> { + super.syncScrollPaneWithViewport(); + } ); + } ); + } else + super.syncScrollPaneWithViewport(); + } + + private void runAndSyncScrollBarValueAnimated( JScrollBar sb, int i, Runnable r ) { + if( inRunAndSyncValueAnimated[i] || sb == null ) { + r.run(); + return; + } + + inRunAndSyncValueAnimated[i] = true; + + int oldValue = sb.getValue(); + int oldVisibleAmount = sb.getVisibleAmount(); + int oldMinimum = sb.getMinimum(); + int oldMaximum = sb.getMaximum(); + + r.run(); + + int newValue = sb.getValue(); + + if( newValue != oldValue && + sb.getVisibleAmount() == oldVisibleAmount && + sb.getMinimum() == oldMinimum && + sb.getMaximum() == oldMaximum && + sb.getUI() instanceof FlatScrollBarUI ) + { + ((FlatScrollBarUI)sb.getUI()).setValueAnimated( oldValue, newValue ); + } + + inRunAndSyncValueAnimated[i] = false; + } + + private final boolean[] inRunAndSyncValueAnimated = new boolean[2]; + //---- class Handler ------------------------------------------------------ /**