mirror of
https://github.com/JFormDesigner/FlatLaf.git
synced 2025-12-08 23:08:42 +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.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;
|
||||
@@ -145,6 +146,21 @@ public class FlatEditorPaneUI
|
||||
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 );
|
||||
@@ -159,6 +175,11 @@ public class FlatEditorPaneUI
|
||||
|
||||
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 ) {
|
||||
|
||||
@@ -34,7 +34,6 @@ 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;
|
||||
@@ -481,7 +480,8 @@ public class FlatScrollBarUI
|
||||
// remember current scrollbar value so that we can start scroll animation from there
|
||||
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
|
||||
// should start at the previous target value
|
||||
if( targetValue != Integer.MIN_VALUE )
|
||||
@@ -510,32 +510,16 @@ 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;
|
||||
private int targetValue = Integer.MIN_VALUE;
|
||||
private boolean useValueIsAdjusting = true;
|
||||
|
||||
int getTargetValue() {
|
||||
return targetValue;
|
||||
}
|
||||
|
||||
public void setValueAnimated( int initialValue, int value ) {
|
||||
// do some check if animation already running
|
||||
if( animator != null && animator.isRunning() && targetValue != Integer.MIN_VALUE ) {
|
||||
|
||||
@@ -17,10 +17,12 @@
|
||||
package com.formdev.flatlaf.ui;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Container;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Insets;
|
||||
import java.awt.KeyboardFocusManager;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ContainerEvent;
|
||||
import java.awt.event.ContainerListener;
|
||||
import java.awt.event.FocusEvent;
|
||||
@@ -31,6 +33,8 @@ import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.ActionMap;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComponent;
|
||||
@@ -458,8 +462,8 @@ public class FlatScrollPaneUI
|
||||
// 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, () -> {
|
||||
runAndSyncScrollBarValueAnimated( scrollpane.getVerticalScrollBar(), 0, false, () -> {
|
||||
runAndSyncScrollBarValueAnimated( scrollpane.getHorizontalScrollBar(), 1, false, () -> {
|
||||
super.syncScrollPaneWithViewport();
|
||||
} );
|
||||
} );
|
||||
@@ -467,8 +471,30 @@ public class FlatScrollPaneUI
|
||||
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();
|
||||
return;
|
||||
}
|
||||
@@ -480,6 +506,10 @@ public class FlatScrollPaneUI
|
||||
int oldMinimum = sb.getMinimum();
|
||||
int oldMaximum = sb.getMaximum();
|
||||
|
||||
FlatScrollBarUI ui = (FlatScrollBarUI) sb.getUI();
|
||||
if( useTargetValue && ui.getTargetValue() != Integer.MIN_VALUE )
|
||||
sb.setValue( ui.getTargetValue() );
|
||||
|
||||
r.run();
|
||||
|
||||
int newValue = sb.getValue();
|
||||
@@ -490,7 +520,7 @@ public class FlatScrollPaneUI
|
||||
sb.getMaximum() == oldMaximum &&
|
||||
sb.getUI() instanceof FlatScrollBarUI )
|
||||
{
|
||||
((FlatScrollBarUI)sb.getUI()).setValueAnimated( oldValue, newValue );
|
||||
ui.setValueAnimated( oldValue, newValue );
|
||||
}
|
||||
|
||||
inRunAndSyncValueAnimated[i] = false;
|
||||
@@ -498,6 +528,53 @@ public class FlatScrollPaneUI
|
||||
|
||||
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 ------------------------------------------------------
|
||||
|
||||
/**
|
||||
@@ -529,4 +606,34 @@ public class FlatScrollPaneUI
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void installKeyboardActions() {
|
||||
super.installKeyboardActions();
|
||||
FlatEditorPaneUI.installKeyboardActions( getComponent() );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Caret createCaret() {
|
||||
return new FlatCaret( null, false );
|
||||
|
||||
@@ -142,6 +142,12 @@ public class FlatTextPaneUI
|
||||
focusListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void installKeyboardActions() {
|
||||
super.installKeyboardActions();
|
||||
FlatEditorPaneUI.installKeyboardActions( getComponent() );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Caret createCaret() {
|
||||
return new FlatCaret( null, false );
|
||||
@@ -156,6 +162,11 @@ public class FlatTextPaneUI
|
||||
|
||||
super.propertyChange( e );
|
||||
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 */
|
||||
|
||||
@@ -238,6 +238,22 @@ public class FlatTreeUI
|
||||
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
|
||||
protected void updateRenderer() {
|
||||
super.updateRenderer();
|
||||
|
||||
Reference in New Issue
Block a user