TabbedPane: support precise scrolling tabs with trackpad (issue #40)

This commit is contained in:
Karl Tauber
2020-10-12 00:33:23 +02:00
parent a46bdef079
commit 3fc85cd7b2
2 changed files with 87 additions and 20 deletions

View File

@@ -132,6 +132,7 @@ public class FlatTabbedPaneUI
protected FlatWheelTabScroller wheelTabScroller; protected FlatWheelTabScroller wheelTabScroller;
private Handler handler; private Handler handler;
private boolean blockRollover;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
return new FlatTabbedPaneUI(); return new FlatTabbedPaneUI();
@@ -282,6 +283,9 @@ public class FlatTabbedPaneUI
@Override @Override
protected void setRolloverTab( int index ) { protected void setRolloverTab( int index ) {
if( blockRollover )
return;
int oldIndex = getRolloverTab(); int oldIndex = getRolloverTab();
super.setRolloverTab( index ); super.setRolloverTab( index );
@@ -659,19 +663,23 @@ public class FlatTabbedPaneUI
protected class FlatWheelTabScroller protected class FlatWheelTabScroller
extends MouseAdapter extends MouseAdapter
{ {
private int lastMouseX;
private int lastMouseY;
private boolean inViewport; private boolean inViewport;
private boolean scrolled; private boolean scrolled;
private Timer timer; private Timer rolloverTimer;
private Timer exitedTimer;
private Animator animator; private Animator animator;
private Point startViewPosition; private Point startViewPosition;
private Point targetViewPosition; private Point targetViewPosition;
private int lastMouseX;
private int lastMouseY;
protected void uninstall() { protected void uninstall() {
if( timer != null ) if( rolloverTimer != null )
timer.stop(); rolloverTimer.stop();
if( exitedTimer != null )
exitedTimer.stop();
if( animator != null ) if( animator != null )
animator.cancel(); animator.cancel();
} }
@@ -686,6 +694,8 @@ public class FlatTabbedPaneUI
lastMouseX = e.getX(); lastMouseX = e.getX();
lastMouseY = e.getY(); lastMouseY = e.getY();
double preciseWheelRotation = e.getPreciseWheelRotation();
// compute new view position // compute new view position
Point viewPosition = (targetViewPosition != null) Point viewPosition = (targetViewPosition != null)
? targetViewPosition ? targetViewPosition
@@ -695,19 +705,34 @@ public class FlatTabbedPaneUI
int y = viewPosition.y; int y = viewPosition.y;
int tabPlacement = tabPane.getTabPlacement(); int tabPlacement = tabPane.getTabPlacement();
if( tabPlacement == TOP || tabPlacement == BOTTOM ) { if( tabPlacement == TOP || tabPlacement == BOTTOM ) {
x += maxTabHeight * e.getWheelRotation(); x += maxTabHeight * preciseWheelRotation;
x = Math.min( Math.max( x, 0 ), viewSize.width - tabViewport.getWidth() ); x = Math.min( Math.max( x, 0 ), viewSize.width - tabViewport.getWidth() );
} else { } else {
y += maxTabHeight * e.getWheelRotation(); y += maxTabHeight * preciseWheelRotation;
y = Math.min( Math.max( y, 0 ), viewSize.height - tabViewport.getHeight() ); y = Math.min( Math.max( y, 0 ), viewSize.height - tabViewport.getHeight() );
} }
// update view position // check whether view position has changed
Point newViewPosition = new Point( x, y ); Point newViewPosition = new Point( x, y );
setViewPositionAnimated( newViewPosition ); if( newViewPosition.equals( viewPosition ) )
return;
if( !newViewPosition.equals( viewPosition )) // update view position
scrolled = true; if( preciseWheelRotation != 0 &&
preciseWheelRotation != e.getWheelRotation() )
{
// do not use animation for precise scrolling (e.g. with trackpad)
// stop running animation (if any)
if( animator != null )
animator.stop();
tabViewport.setViewPosition( newViewPosition );
updateRolloverDelayed();
} else
setViewPositionAnimated( newViewPosition );
scrolled = true;
} }
protected void setViewPositionAnimated( Point viewPosition ) { protected void setViewPositionAnimated( Point viewPosition ) {
@@ -718,7 +743,7 @@ public class FlatTabbedPaneUI
// do not use animation if disabled // do not use animation if disabled
if( !isSmoothScrollingEnabled() ) { if( !isSmoothScrollingEnabled() ) {
tabViewport.setViewPosition( viewPosition ); tabViewport.setViewPosition( viewPosition );
setRolloverTab( lastMouseX, lastMouseY ); updateRolloverDelayed();
return; return;
} }
@@ -754,8 +779,38 @@ public class FlatTabbedPaneUI
} }
// restart animator // restart animator
animator.cancel(); animator.restart();
animator.start(); }
protected void updateRolloverDelayed() {
blockRollover = true;
// keep rollover on last tab until it would move to another tab, then clear it
int oldIndex = getRolloverTab();
if( oldIndex >= 0 ) {
int index = tabForCoordinate( tabPane, lastMouseX, lastMouseY );
if( index >= 0 && index != oldIndex ) {
// clear if moved to another tab
blockRollover = false;
setRolloverTab( -1 );
blockRollover = true;
}
}
// create timer
if( rolloverTimer == null ) {
rolloverTimer = new Timer( 150, e -> {
blockRollover = false;
// highlight tab at mouse location
if( tabPane != null )
setRolloverTab( lastMouseX, lastMouseY );
} );
rolloverTimer.setRepeats( false );
}
// restart timer
rolloverTimer.restart();
} }
@Override @Override
@@ -790,8 +845,8 @@ public class FlatTabbedPaneUI
if( inViewport != wasInViewport ) { if( inViewport != wasInViewport ) {
if( !inViewport ) if( !inViewport )
viewportExited(); viewportExited();
else if( timer != null ) else if( exitedTimer != null )
timer.stop(); exitedTimer.stop();
} }
} }
@@ -799,12 +854,12 @@ public class FlatTabbedPaneUI
if( !scrolled ) if( !scrolled )
return; return;
if( timer == null ) { if( exitedTimer == null ) {
timer = new Timer( 500, e -> ensureSelectedTabVisible() ); exitedTimer = new Timer( 500, e -> ensureSelectedTabVisible() );
timer.setRepeats( false ); exitedTimer.setRepeats( false );
} }
timer.start(); exitedTimer.start();
} }
protected void ensureSelectedTabVisible() { protected void ensureSelectedTabVisible() {

View File

@@ -216,6 +216,9 @@ public class Animator
} }
private void stop( boolean cancel ) { private void stop( boolean cancel ) {
if( !running )
return;
if( timer != null ) if( timer != null )
timer.stop(); timer.stop();
@@ -226,6 +229,15 @@ public class Animator
timeToStop = false; timeToStop = false;
} }
/**
* Restarts the animation.
* Invokes {@link #cancel()} and {@link #start()}.
*/
public void restart() {
cancel();
start();
}
/** /**
* Returns whether this animation is running. * Returns whether this animation is running.
*/ */