mirror of
https://github.com/JFormDesigner/FlatLaf.git
synced 2025-12-08 15:00:54 +03:00
Smooth Scrolling: fixes too slow repeating block (page) scrolling (e.g. hold down PageUp key) for Tree, TextArea, TextPane and EditorPane
This commit is contained in:
@@ -31,6 +31,7 @@ import javax.swing.UIManager;
|
|||||||
import javax.swing.plaf.ComponentUI;
|
import javax.swing.plaf.ComponentUI;
|
||||||
import javax.swing.plaf.basic.BasicEditorPaneUI;
|
import javax.swing.plaf.basic.BasicEditorPaneUI;
|
||||||
import javax.swing.text.Caret;
|
import javax.swing.text.Caret;
|
||||||
|
import javax.swing.text.DefaultEditorKit;
|
||||||
import javax.swing.text.JTextComponent;
|
import javax.swing.text.JTextComponent;
|
||||||
import com.formdev.flatlaf.FlatClientProperties;
|
import com.formdev.flatlaf.FlatClientProperties;
|
||||||
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
|
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
|
||||||
@@ -145,6 +146,21 @@ public class FlatEditorPaneUI
|
|||||||
focusListener = null;
|
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
|
@Override
|
||||||
protected Caret createCaret() {
|
protected Caret createCaret() {
|
||||||
return new FlatCaret( null, false );
|
return new FlatCaret( null, false );
|
||||||
@@ -159,6 +175,11 @@ public class FlatEditorPaneUI
|
|||||||
|
|
||||||
super.propertyChange( e );
|
super.propertyChange( e );
|
||||||
propertyChange( getComponent(), e, this::installStyle );
|
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 ) {
|
static void propertyChange( JTextComponent c, PropertyChangeEvent e, Runnable installStyle ) {
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ 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;
|
||||||
@@ -481,7 +480,8 @@ 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();
|
||||||
|
|
||||||
runWithoutBlitting( scrollbar.getParent(), () ->{
|
// 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
|
// if invoked while animation is running, calculation of new value
|
||||||
// should start at the previous target value
|
// should start at the previous target value
|
||||||
if( targetValue != Integer.MIN_VALUE )
|
if( targetValue != Integer.MIN_VALUE )
|
||||||
@@ -510,32 +510,16 @@ 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;
|
||||||
private int targetValue = Integer.MIN_VALUE;
|
private int targetValue = Integer.MIN_VALUE;
|
||||||
private boolean useValueIsAdjusting = true;
|
private boolean useValueIsAdjusting = true;
|
||||||
|
|
||||||
|
int getTargetValue() {
|
||||||
|
return targetValue;
|
||||||
|
}
|
||||||
|
|
||||||
public void setValueAnimated( int initialValue, int value ) {
|
public void setValueAnimated( int initialValue, int value ) {
|
||||||
// do some check if animation already running
|
// do some check if animation already running
|
||||||
if( animator != null && animator.isRunning() && targetValue != Integer.MIN_VALUE ) {
|
if( animator != null && animator.isRunning() && targetValue != Integer.MIN_VALUE ) {
|
||||||
|
|||||||
@@ -17,10 +17,12 @@
|
|||||||
package com.formdev.flatlaf.ui;
|
package com.formdev.flatlaf.ui;
|
||||||
|
|
||||||
import java.awt.Component;
|
import java.awt.Component;
|
||||||
|
import java.awt.Container;
|
||||||
import java.awt.Graphics;
|
import java.awt.Graphics;
|
||||||
import java.awt.Insets;
|
import java.awt.Insets;
|
||||||
import java.awt.KeyboardFocusManager;
|
import java.awt.KeyboardFocusManager;
|
||||||
import java.awt.Rectangle;
|
import java.awt.Rectangle;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ContainerEvent;
|
import java.awt.event.ContainerEvent;
|
||||||
import java.awt.event.ContainerListener;
|
import java.awt.event.ContainerListener;
|
||||||
import java.awt.event.FocusEvent;
|
import java.awt.event.FocusEvent;
|
||||||
@@ -31,6 +33,8 @@ import java.beans.PropertyChangeEvent;
|
|||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import javax.swing.Action;
|
||||||
|
import javax.swing.ActionMap;
|
||||||
import javax.swing.BorderFactory;
|
import javax.swing.BorderFactory;
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
import javax.swing.JComponent;
|
import javax.swing.JComponent;
|
||||||
@@ -458,8 +462,8 @@ public class FlatScrollPaneUI
|
|||||||
// if the viewport has been scrolled by using JComponent.scrollRectToVisible()
|
// 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
|
// (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, false, () -> {
|
||||||
runAndSyncScrollBarValueAnimated( scrollpane.getHorizontalScrollBar(), 1, () -> {
|
runAndSyncScrollBarValueAnimated( scrollpane.getHorizontalScrollBar(), 1, false, () -> {
|
||||||
super.syncScrollPaneWithViewport();
|
super.syncScrollPaneWithViewport();
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
@@ -467,8 +471,30 @@ public class FlatScrollPaneUI
|
|||||||
super.syncScrollPaneWithViewport();
|
super.syncScrollPaneWithViewport();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void runAndSyncScrollBarValueAnimated( JScrollBar sb, int i, Runnable r ) {
|
/**
|
||||||
if( inRunAndSyncValueAnimated[i] || sb == null ) {
|
* Runs the given runnable, if smooth scrolling is enabled, with disabled
|
||||||
|
* viewport blitting mode and with scroll bar value set to "target" value.
|
||||||
|
* This is necessary when calculating new view position during animation.
|
||||||
|
* Otherwise calculation would use wrong view position and (repeating) scrolling
|
||||||
|
* would be much slower than without smooth scrolling.
|
||||||
|
*/
|
||||||
|
private void runWithScrollBarsTargetValues( boolean blittingOnly, Runnable r ) {
|
||||||
|
if( isSmoothScrollingEnabled() ) {
|
||||||
|
runWithoutBlitting( scrollpane, () -> {
|
||||||
|
if( blittingOnly )
|
||||||
|
r.run();
|
||||||
|
else {
|
||||||
|
runAndSyncScrollBarValueAnimated( scrollpane.getVerticalScrollBar(), 0, true, () -> {
|
||||||
|
runAndSyncScrollBarValueAnimated( scrollpane.getHorizontalScrollBar(), 1, true, r );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
} else
|
||||||
|
r.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runAndSyncScrollBarValueAnimated( JScrollBar sb, int i, boolean useTargetValue, Runnable r ) {
|
||||||
|
if( inRunAndSyncValueAnimated[i] || sb == null || !(sb.getUI() instanceof FlatScrollBarUI) ) {
|
||||||
r.run();
|
r.run();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -480,6 +506,10 @@ public class FlatScrollPaneUI
|
|||||||
int oldMinimum = sb.getMinimum();
|
int oldMinimum = sb.getMinimum();
|
||||||
int oldMaximum = sb.getMaximum();
|
int oldMaximum = sb.getMaximum();
|
||||||
|
|
||||||
|
FlatScrollBarUI ui = (FlatScrollBarUI) sb.getUI();
|
||||||
|
if( useTargetValue && ui.getTargetValue() != Integer.MIN_VALUE )
|
||||||
|
sb.setValue( ui.getTargetValue() );
|
||||||
|
|
||||||
r.run();
|
r.run();
|
||||||
|
|
||||||
int newValue = sb.getValue();
|
int newValue = sb.getValue();
|
||||||
@@ -490,7 +520,7 @@ public class FlatScrollPaneUI
|
|||||||
sb.getMaximum() == oldMaximum &&
|
sb.getMaximum() == oldMaximum &&
|
||||||
sb.getUI() instanceof FlatScrollBarUI )
|
sb.getUI() instanceof FlatScrollBarUI )
|
||||||
{
|
{
|
||||||
((FlatScrollBarUI)sb.getUI()).setValueAnimated( oldValue, newValue );
|
ui.setValueAnimated( oldValue, newValue );
|
||||||
}
|
}
|
||||||
|
|
||||||
inRunAndSyncValueAnimated[i] = false;
|
inRunAndSyncValueAnimated[i] = false;
|
||||||
@@ -498,6 +528,53 @@ public class FlatScrollPaneUI
|
|||||||
|
|
||||||
private final boolean[] inRunAndSyncValueAnimated = new boolean[2];
|
private final boolean[] inRunAndSyncValueAnimated = new boolean[2];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the given runnable with disabled viewport blitting mode.
|
||||||
|
* If blitting mode is enabled, the viewport immediately repaints parts of the
|
||||||
|
* view if the view position is changed via JViewport.setViewPosition().
|
||||||
|
* This causes scrolling artifacts if smooth scrolling is enabled and the view position
|
||||||
|
* is "temporary" changed to its new target position, changed back to its old position
|
||||||
|
* and again moved animated to the target position.
|
||||||
|
*/
|
||||||
|
static 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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void installSmoothScrollingDelegateActions( JComponent c, boolean blittingOnly, String... actionKeys ) {
|
||||||
|
// get shared action map, used for all components of same type
|
||||||
|
ActionMap map = SwingUtilities.getUIActionMap( c );
|
||||||
|
if( map == null )
|
||||||
|
return;
|
||||||
|
|
||||||
|
// install actions, but only if not already installed
|
||||||
|
for( String actionKey : actionKeys )
|
||||||
|
installSmoothScrollingDelegateAction( map, blittingOnly, actionKey );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void installSmoothScrollingDelegateAction( ActionMap map, boolean blittingOnly, String actionKey ) {
|
||||||
|
Action oldAction = map.get( actionKey );
|
||||||
|
if( oldAction == null || oldAction instanceof SmoothScrollingDelegateAction )
|
||||||
|
return; // not found or already installed
|
||||||
|
|
||||||
|
map.put( actionKey, new SmoothScrollingDelegateAction( oldAction, blittingOnly ) );
|
||||||
|
}
|
||||||
|
|
||||||
//---- class Handler ------------------------------------------------------
|
//---- class Handler ------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -529,4 +606,34 @@ public class FlatScrollPaneUI
|
|||||||
scrollpane.repaint();
|
scrollpane.repaint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//---- class SmoothScrollingDelegateAction --------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to run component actions with disabled blitting mode and
|
||||||
|
* with scroll bar target values.
|
||||||
|
*/
|
||||||
|
private static class SmoothScrollingDelegateAction
|
||||||
|
extends FlatUIAction
|
||||||
|
{
|
||||||
|
private final boolean blittingOnly;
|
||||||
|
|
||||||
|
private SmoothScrollingDelegateAction( Action delegate, boolean blittingOnly ) {
|
||||||
|
super( delegate );
|
||||||
|
this.blittingOnly = blittingOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed( ActionEvent e ) {
|
||||||
|
Object source = e.getSource();
|
||||||
|
JScrollPane scrollPane = (source instanceof Component)
|
||||||
|
? (JScrollPane) SwingUtilities.getAncestorOfClass( JScrollPane.class, (Component) source )
|
||||||
|
: null;
|
||||||
|
if( scrollPane != null && scrollPane.getUI() instanceof FlatScrollPaneUI ) {
|
||||||
|
((FlatScrollPaneUI)scrollPane.getUI()).runWithScrollBarsTargetValues( blittingOnly,
|
||||||
|
() -> delegate.actionPerformed( e ) );
|
||||||
|
} else
|
||||||
|
delegate.actionPerformed( e );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,6 +141,12 @@ public class FlatTextAreaUI
|
|||||||
focusListener = null;
|
focusListener = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void installKeyboardActions() {
|
||||||
|
super.installKeyboardActions();
|
||||||
|
FlatEditorPaneUI.installKeyboardActions( getComponent() );
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Caret createCaret() {
|
protected Caret createCaret() {
|
||||||
return new FlatCaret( null, false );
|
return new FlatCaret( null, false );
|
||||||
|
|||||||
@@ -142,6 +142,12 @@ public class FlatTextPaneUI
|
|||||||
focusListener = null;
|
focusListener = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void installKeyboardActions() {
|
||||||
|
super.installKeyboardActions();
|
||||||
|
FlatEditorPaneUI.installKeyboardActions( getComponent() );
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Caret createCaret() {
|
protected Caret createCaret() {
|
||||||
return new FlatCaret( null, false );
|
return new FlatCaret( null, false );
|
||||||
@@ -156,6 +162,11 @@ public class FlatTextPaneUI
|
|||||||
|
|
||||||
super.propertyChange( e );
|
super.propertyChange( e );
|
||||||
FlatEditorPaneUI.propertyChange( getComponent(), e, this::installStyle );
|
FlatEditorPaneUI.propertyChange( getComponent(), e, this::installStyle );
|
||||||
|
|
||||||
|
// BasicEditorPaneUI.propertyChange() re-applied actions from editor kit,
|
||||||
|
// which removed our delegate actions
|
||||||
|
if( "editorKit".equals( propertyName ) )
|
||||||
|
FlatEditorPaneUI.installKeyboardActions( getComponent() );
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @since 2 */
|
/** @since 2 */
|
||||||
|
|||||||
@@ -238,6 +238,22 @@ public class FlatTreeUI
|
|||||||
oldStyleValues = null;
|
oldStyleValues = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void installKeyboardActions() {
|
||||||
|
super.installKeyboardActions();
|
||||||
|
|
||||||
|
FlatScrollPaneUI.installSmoothScrollingDelegateActions( tree, false,
|
||||||
|
"scrollDownChangeSelection", // PAGE_DOWN
|
||||||
|
"scrollUpChangeSelection", // PAGE_UP
|
||||||
|
"scrollDownChangeLead", // ctrl PAGE_DOWN
|
||||||
|
"scrollUpChangeLead", // ctrl PAGE_UP
|
||||||
|
"scrollDownExtendSelection", // shift PAGE_DOWN, shift ctrl PAGE_DOWN
|
||||||
|
"scrollUpExtendSelection", // shift PAGE_UP, shift ctrl PAGE_UP
|
||||||
|
"selectNextChangeLead", // ctrl DOWN
|
||||||
|
"selectPreviousChangeLead" // ctrl UP
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void updateRenderer() {
|
protected void updateRenderer() {
|
||||||
super.updateRenderer();
|
super.updateRenderer();
|
||||||
|
|||||||
Reference in New Issue
Block a user