Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
7f226a2
ScrollBar: use smooth scrolling when clicking on track or on arrow bu…
DevCharly Jul 25, 2020
b67b701
ScrollPane: use smooth scrolling when rotating the mouse wheel (issue…
DevCharly Jul 25, 2020
82514cc
Demo: added "Options > Smooth Scrolling" to menu (issue #50)
DevCharly Jul 25, 2020
889b5ea
ScrollBar: fixed smooth scrolling issues when continuously scrolling …
DevCharly Jul 25, 2020
7363058
ScrollBar: set valueIsAdjusting property to true while smooth scrolli…
DevCharly Aug 4, 2020
fdabca9
ScrollBar: fixed NPE when switching LaF while smooth scrolling animat…
DevCharly Aug 7, 2020
1ebfe00
added system properties "flatlaf.animation" and "flatlaf.smoothScroll…
DevCharly Aug 8, 2020
762fe89
FlatSmoothScrollingTest: added JTree, JTable, JTextArea, JTextPane an…
DevCharly Aug 8, 2020
7a582c2
ScrollBar: fixed issue with updating thumb location (regressing since…
DevCharly Aug 8, 2020
522ebb6
FlatSmoothScrollingTest: allow enabling/disabling smooth scrolling wi…
DevCharly Aug 9, 2020
e603bd8
FlatSmoothScrollingTest: added simple line chart that shows changes t…
DevCharly Aug 10, 2020
d64a8e9
FlatSmoothScrollingTest:
DevCharly Aug 10, 2020
1ae3158
FlatSmoothScrollingTest: paint "temporary" scrollbar values in line c…
DevCharly Aug 10, 2020
305e9e6
ScrollBar: fixed jittery scrolling when in repeating mode (hold down …
DevCharly Aug 11, 2020
1f26228
FlatSmoothScrollingTest: support dark themes and added "Show table gr…
DevCharly Aug 11, 2020
3573188
ScrollBar: support smooth scrolling via keyboard
DevCharly Aug 12, 2020
865a568
FlatSmoothScrollingTest: added "custom" scroll pane for testing smoot…
DevCharly Aug 12, 2020
419a689
FlatAnimatorTest: added test for wheel scrolling (including chart)
DevCharly Oct 9, 2020
29f6c5f
FlatAnimatorTest: added test for precise scrolling with trackpad
DevCharly Oct 11, 2020
cf70cfb
ScrollBar: fixed temporary painting at wrong location during smooth s…
DevCharly Jun 4, 2023
e2e3fd3
FlatSmoothScrollingTest:
DevCharly Jun 5, 2023
6ce2198
FlatSmoothScrollingTest:
DevCharly Aug 23, 2023
3628a03
introduced FlatUIAction
DevCharly Aug 24, 2023
542e7d5
Smooth Scrolling: fixes too slow repeating block (page) scrolling (e.…
DevCharly Aug 24, 2023
6dfc204
SmoothScrollingTest added (from https://github.com/JFormDesigner/Flat…
DevCharly Aug 24, 2023
5cdef54
Smooth Scrolling: fixed jittery scrolling with trackpad or Magic Mous…
DevCharly Aug 25, 2023
04658c2
SmoothScrollingTest: fixed error reported by Error Prone
DevCharly Aug 25, 2023
c529dcb
Smooth Scrolling:
DevCharly Aug 27, 2023
b32b8db
FlatSmoothScrollingTest: refactored line chart panel into own class f…
DevCharly Aug 28, 2023
44a04cc
FlatSmoothScrollingTest:
DevCharly Sep 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,14 @@ public interface FlatSystemProperties
*/
String ANIMATION = "flatlaf.animation";

/**
* Specifies whether smooth scrolling is enabled.
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> {@code true}
*/
String SMOOTH_SCROLLING = "flatlaf.smoothScrolling";

/**
* Specifies whether vertical text position is corrected when UI is scaled on HiDPI screens.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicEditorPaneUI;
import javax.swing.text.Caret;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
Expand Down Expand Up @@ -145,6 +146,21 @@ protected void uninstallListeners() {
focusListener = null;
}

@Override
protected void installKeyboardActions() {
super.installKeyboardActions();
installKeyboardActions( getComponent() );
}

static void installKeyboardActions( JTextComponent c ) {
FlatScrollPaneUI.installSmoothScrollingDelegateActions( c, false,
/* page-down */ DefaultEditorKit.pageDownAction, // PAGE_DOWN
/* page-up */ DefaultEditorKit.pageUpAction, // PAGE_UP
/* DefaultEditorKit.selectionPageDownAction */ "selection-page-down", // shift PAGE_DOWN
/* DefaultEditorKit.selectionPageUpAction */ "selection-page-up" // shift PAGE_UP
);
}

@Override
protected Caret createCaret() {
return new FlatCaret( null, false );
Expand All @@ -159,6 +175,11 @@ protected void propertyChange( PropertyChangeEvent e ) {

super.propertyChange( e );
propertyChange( getComponent(), e, this::installStyle );

// BasicEditorPaneUI.propertyChange() re-applied actions from editor kit,
// which removed our delegate actions
if( "editorKit".equals( propertyName ) )
installKeyboardActions( getComponent() );
}

static void propertyChange( JTextComponent c, PropertyChangeEvent e, Runnable installStyle ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import java.beans.PropertyChangeListener;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
Expand Down Expand Up @@ -149,7 +148,7 @@ protected void installKeyboardActions() {
map = new ActionMapUIResource();
SwingUtilities.replaceUIActionMap( menuBar, map );
}
map.put( "takeFocus", new TakeFocus() );
map.put( "takeFocus", new TakeFocus( "takeFocus" ) );
}

/** @since 2 */
Expand Down Expand Up @@ -373,8 +372,12 @@ public void layoutContainer( Container target ) {
* On other platforms, the popup of the first menu is shown.
*/
private static class TakeFocus
extends AbstractAction
extends FlatUIAction
{
public TakeFocus( String name ) {
super( name );
}

@Override
public void actionPerformed( ActionEvent e ) {
JMenuBar menuBar = (JMenuBar) e.getSource();
Expand Down
205 changes: 205 additions & 0 deletions flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatScrollBarUI.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeListener;
Expand All @@ -39,10 +40,13 @@
import javax.swing.plaf.basic.BasicScrollBarUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.Animator;
import com.formdev.flatlaf.util.CubicBezierEasing;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
Expand Down Expand Up @@ -203,6 +207,16 @@ protected void uninstallDefaults() {
oldStyleValues = null;
}

@Override
protected TrackListener createTrackListener() {
return new FlatTrackListener();
}

@Override
protected ScrollListener createScrollListener() {
return new FlatScrollListener();
}

@Override
protected PropertyChangeListener createPropertyChangeListener() {
PropertyChangeListener superListener = super.createPropertyChangeListener();
Expand Down Expand Up @@ -431,6 +445,197 @@ public boolean getSupportsAbsolutePositioning() {
return allowsAbsolutePositioning;
}

@Override
protected void scrollByBlock( int direction ) {
runAndSetValueAnimated( () -> {
super.scrollByBlock( direction );
} );
}

@Override
protected void scrollByUnit( int direction ) {
runAndSetValueAnimated( () -> {
super.scrollByUnit( direction );
} );
}

/**
* Runs the given runnable, which should modify the scroll bar value,
* and then animate scroll bar value from old value to new value.
*/
public void runAndSetValueAnimated( Runnable r ) {
if( inRunAndSetValueAnimated || !isSmoothScrollingEnabled() ) {
r.run();
return;
}

inRunAndSetValueAnimated = true;

if( animator != null )
animator.cancel();

if( useValueIsAdjusting )
scrollbar.setValueIsAdjusting( true );

// remember current scrollbar value so that we can start scroll animation from there
int oldValue = scrollbar.getValue();

// run given runnable, which computes and sets the new scrollbar value
FlatScrollPaneUI.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();
} );

// 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 ) {
// 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 boolean useValueIsAdjusting = true;

int getTargetValue() {
return targetValue;
}

public void setValueAnimated( int initialValue, int value ) {
if( useValueIsAdjusting )
scrollbar.setValueIsAdjusting( true );

// (always) set scrollbar value to initial value
scrollbar.setValue( initialValue );

// 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.
// Without this check, repeating-scrolling via keyboard would become
// very slow when reaching the top/bottom/left/right of the viewport,
// because it would start a new 200ms animation to scroll a few pixels.
if( value == targetValue ||
(value > startValue && value < targetValue) || // scroll down/right
(value < startValue && value > targetValue) ) // scroll up/left
return;
}

startValue = initialValue;
targetValue = value;

// create animator
if( animator == null ) {
int duration = FlatUIUtils.getUIInt( "ScrollPane.smoothScrolling.duration", 200 );
int resolution = FlatUIUtils.getUIInt( "ScrollPane.smoothScrolling.resolution", 10 );
Object interpolator = UIManager.get( "ScrollPane.smoothScrolling.interpolator" );

animator = new Animator( duration, fraction -> {
if( scrollbar == null || !scrollbar.isShowing() ) {
animator.stop();
return;
}

// re-enable valueIsAdjusting if disabled while animation is running
// (e.g. in mouse released listener)
if( useValueIsAdjusting && !scrollbar.getValueIsAdjusting() )
scrollbar.setValueIsAdjusting( true );

scrollbar.setValue( startValue + Math.round( (targetValue - startValue) * fraction ) );
}, () -> {
startValue = targetValue = Integer.MIN_VALUE;

if( useValueIsAdjusting && scrollbar != null )
scrollbar.setValueIsAdjusting( false );
});

animator.setResolution( resolution );
animator.setInterpolator( (interpolator instanceof Animator.Interpolator)
? (Animator.Interpolator) interpolator
: new CubicBezierEasing( 0.5f, 0.5f, 0.5f, 1 ) );
}

// restart animator
animator.cancel();
animator.start();
}

protected boolean isSmoothScrollingEnabled() {
if( !Animator.useAnimation() || !FlatSystemProperties.getBoolean( FlatSystemProperties.SMOOTH_SCROLLING, true ) )
return false;

// if scroll bar is child of scroll pane, check only client property of scroll pane
Container parent = scrollbar.getParent();
JComponent c = (parent instanceof JScrollPane) ? (JScrollPane) parent : scrollbar;
Object smoothScrolling = c.getClientProperty( FlatClientProperties.SCROLL_PANE_SMOOTH_SCROLLING );
if( smoothScrolling instanceof Boolean )
return (Boolean) smoothScrolling;

// Note: Getting UI value "ScrollPane.smoothScrolling" here to allow
// applications to turn smooth scrolling on or off at any time
// (e.g. in application options dialog).
return UIManager.getBoolean( "ScrollPane.smoothScrolling" );
}

//---- class FlatTrackListener --------------------------------------------

protected class FlatTrackListener
extends TrackListener
{
@Override
public void mousePressed( MouseEvent e ) {
// Do not use valueIsAdjusting here (in runAndSetValueAnimated())
// for smooth scrolling because super.mousePressed() enables this itself
// and super.mouseRelease() disables it later.
// If we would disable valueIsAdjusting here (in runAndSetValueAnimated())
// and move the thumb with the mouse, then the thumb location is not updated
// if later scrolled with a key (e.g. HOME key).
useValueIsAdjusting = false;

runAndSetValueAnimated( () -> {
super.mousePressed( e );
} );
}

@Override
public void mouseReleased( MouseEvent e ) {
super.mouseReleased( e );
useValueIsAdjusting = true;
}
}

//---- class FlatScrollListener -------------------------------------------

protected class FlatScrollListener
extends ScrollListener
{
@Override
public void actionPerformed( ActionEvent e ) {
runAndSetValueAnimated( () -> {
super.actionPerformed( e );
} );
}
}

//---- class ScrollBarHoverListener ---------------------------------------

// using static field to disabling hover for other scroll bars
Expand Down
Loading