support smooth scrolling with touchpads and high precision mouse wheels (issue #27)

This commit is contained in:
Karl Tauber
2020-01-09 23:43:58 +01:00
parent 43ab095e0f
commit fd9dbbd7e6
3 changed files with 142 additions and 0 deletions

View File

@@ -3,6 +3,8 @@ FlatLaf Change Log
## Unreleased
- Support smooth scrolling with touchpads and high precision mouse wheels.
(issue #27)
- Changed `.properties` file loading order: Now all core `.properties` files are
loaded before loading addon `.properties` files. This makes it easier to
overwrite core values in addons. Also, addon loading order can be specified.

View File

@@ -14,15 +14,24 @@
* limitations under the License.
*/
/*
* Smooth scrolling code partly based on code from IntelliJ IDEA Community Edition,
* which is licensed under the Apache 2.0 license. Copyright 2000-2016 JetBrains s.r.o.
* See: https://github.com/JetBrains/intellij-community/blob/31e1b5a8e43219b9571951bab6457cfb3012e3ef/platform/platform-api/src/com/intellij/ui/components/SmoothScrollPane.java#L141-L185
*
*/
package com.formdev.flatlaf.ui;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.BorderFactory;
@@ -34,6 +43,8 @@ import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.LookAndFeel;
import javax.swing.ScrollPaneConstants;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicScrollPaneUI;
@@ -50,6 +61,10 @@ import com.formdev.flatlaf.FlatClientProperties;
* @uiDefault ScrollPane.border Border
* @uiDefault ScrollPane.viewportBorder Border
*
* <!-- FlatScrollPaneUI -->
*
* @uiDefault ScrollPane.smoothScrolling boolean
*
* @author Karl Tauber
*/
public class FlatScrollPaneUI
@@ -94,6 +109,130 @@ public class FlatScrollPaneUI
handler = null;
}
@Override
protected MouseWheelListener createMouseWheelListener() {
return new BasicScrollPaneUI.MouseWheelHandler() {
@Override
public void mouseWheelMoved( MouseWheelEvent e ) {
// 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).
if( UIManager.getBoolean( "ScrollPane.smoothScrolling" ) &&
scrollpane.isWheelScrollingEnabled() &&
e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL &&
e.getPreciseWheelRotation() != 0 &&
e.getPreciseWheelRotation() != e.getWheelRotation() )
{
mouseWheelMovedSmooth( e );
} else
super.mouseWheelMoved( e );
}
};
}
private static final double EPSILON = 1e-5d;
private void mouseWheelMovedSmooth( MouseWheelEvent e ) {
// return if there is no viewport
JViewport viewport = scrollpane.getViewport();
if( viewport == null )
return;
// find scrollbar to scroll
JScrollBar scrollbar = scrollpane.getVerticalScrollBar();
if( scrollbar == null || !scrollbar.isVisible() || e.isShiftDown() ) {
scrollbar = scrollpane.getHorizontalScrollBar();
if( scrollbar == null || !scrollbar.isVisible() )
return;
}
// consume event
e.consume();
// get precise wheel rotation
double rotation = e.getPreciseWheelRotation();
// get unit and block increment
int unitIncrement;
int blockIncrement;
int orientation = scrollbar.getOrientation();
Component view = viewport.getView();
if( view instanceof Scrollable ) {
Scrollable scrollable = (Scrollable) view;
// Use (0, 0) view position to obtain constant unit increment of first item
// (which might otherwise be variable on smaller-than-unit scrolling).
Rectangle visibleRect = new Rectangle( viewport.getViewSize() );
unitIncrement = scrollable.getScrollableUnitIncrement( visibleRect, orientation, 1 );
blockIncrement = scrollable.getScrollableBlockIncrement( visibleRect, orientation, 1 );
if( unitIncrement > 0 ) {
// For the case that the first item (e.g. in a list) is larger
// than the other items, get the unit increment of the second item
// and use the smaller one.
if( orientation == SwingConstants.VERTICAL ) {
visibleRect.y += unitIncrement;
visibleRect.height -= unitIncrement;
} else {
visibleRect.x += unitIncrement;
visibleRect.width -= unitIncrement;
}
int unitIncrement2 = scrollable.getScrollableUnitIncrement( visibleRect, orientation, 1 );
if( unitIncrement2 > 0 )
unitIncrement = Math.min( unitIncrement, unitIncrement2 );
}
} else {
int direction = rotation < 0 ? -1 : 1;
unitIncrement = scrollbar.getUnitIncrement( direction );
blockIncrement = scrollbar.getBlockIncrement( direction );
}
// limit scroll amount (number of units to scroll) for small viewports
// (e.g. vertical scrolling in file chooser)
int scrollAmount = e.getScrollAmount();
int viewportWH = (orientation == SwingConstants.VERTICAL)
? viewport.getHeight()
: viewport.getWidth();
if( unitIncrement * scrollAmount > viewportWH )
scrollAmount = Math.max( viewportWH / unitIncrement, 1 );
// compute relative delta
double delta = rotation * scrollAmount * unitIncrement;
boolean adjustDelta = Math.abs( rotation ) < (1.0 + EPSILON);
double adjustedDelta = adjustDelta
? Math.max( -blockIncrement, Math.min( delta, blockIncrement ) )
: delta;
// compute new value
int value = scrollbar.getValue();
double minDelta = scrollbar.getMinimum() - value;
double maxDelta = scrollbar.getMaximum() - scrollbar.getModel().getExtent() - value;
double boundedDelta = Math.max( minDelta, Math.min( adjustedDelta, maxDelta ) );
int newValue = value + (int) Math.round( boundedDelta );
// set new value
if( newValue != value )
scrollbar.setValue( newValue );
/*debug
System.out.println( String.format( "%4d %9f / %4d %4d / %12f %5s %12f / %4d %4d %4d / %12f %12f %12f / %4d",
e.getWheelRotation(),
e.getPreciseWheelRotation(),
unitIncrement,
blockIncrement,
delta,
adjustDelta,
adjustedDelta,
value,
scrollbar.getMinimum(),
scrollbar.getMaximum(),
minDelta,
maxDelta,
boundedDelta,
newValue ) );
*/
}
@Override
protected PropertyChangeListener createPropertyChangeListener() {
return new BasicScrollPaneUI.PropertyChangeHandler() {

View File

@@ -311,6 +311,7 @@ ScrollBar.buttonDisabledArrowColor=$ComboBox.buttonDisabledArrowColor
ScrollPane.border=com.formdev.flatlaf.ui.FlatBorder
ScrollPane.background=$ScrollBar.track
ScrollPane.fillUpperCorner=true
ScrollPane.smoothScrolling=true
#---- Separator ----