diff --git a/CHANGELOG.md b/CHANGELOG.md index 441ea971..c6e1f611 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,8 @@ FlatLaf Change Log `tab`) now respect explicitly set background color. - TabbedPane: Fixed `IndexOutOfBoundsException` when using tooltip text on close buttons and closing last/rightmost tab. (issue #235) +- TabbedPane: Fixed scrolling tabs with touchpads and high-resolution mouse + wheels. - Extras: Added missing export of package `com.formdev.flatlaf.extras.components` to Java 9 module descriptor. - JIDE Common Layer: diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java index 81502f75..1705c909 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java @@ -221,6 +221,8 @@ public class FlatTabbedPaneUI private Container leadingComponent; private Container trailingComponent; + private Dimension scrollBackwardButtonPrefSize; + private Handler handler; private boolean blockRollover; private boolean rolloverTabClose; @@ -1870,33 +1872,78 @@ public class FlatTabbedPaneUI lastMouseY = e.getY(); double preciseWheelRotation = e.getPreciseWheelRotation(); + boolean isPreciseWheel = (preciseWheelRotation != 0 && preciseWheelRotation != e.getWheelRotation()); int amount = (int) (maxTabHeight * preciseWheelRotation); + // scroll at least one pixel to avoid "hanging" + if( amount == 0 ) { + if( preciseWheelRotation > 0 ) + amount = 1; + else if( preciseWheelRotation < 0 ) + amount = -1; + } + // compute new view position Point viewPosition = (targetViewPosition != null) ? targetViewPosition : tabViewport.getViewPosition(); Dimension viewSize = tabViewport.getViewSize(); + boolean horizontal = isHorizontalTabPlacement(); int x = viewPosition.x; int y = viewPosition.y; - int tabPlacement = tabPane.getTabPlacement(); - if( tabPlacement == TOP || tabPlacement == BOTTOM ) { + if( horizontal ) x += isLeftToRight() ? amount : -amount; - x = Math.min( Math.max( x, 0 ), viewSize.width - tabViewport.getWidth() ); - } else { + else y += amount; - y = Math.min( Math.max( y, 0 ), viewSize.height - tabViewport.getHeight() ); + + // In case of having scroll buttons on both sides and hiding disabled buttons, + // the viewport is moved when the scroll backward button becomes visible + // or is hidden. For non-precise wheel scrolling (e.g. mouse wheel on Windows), + // this is no problem because the scroll amount is at least a tab-height. + // For precise wheel scrolling (e.g. touchpad on Mac), this is a problem + // because it is possible to scroll by a fraction of a tab-height. + if( isPreciseWheel && + getScrollButtonsPlacement() == BOTH && + getScrollButtonsPolicy() == AS_NEEDED_SINGLE && + (isLeftToRight() || !horizontal) || // scroll buttons are hidden in right-to-left + scrollBackwardButtonPrefSize != null ) + { + // special cases for scrolling with touchpad or high-resolution wheel: + // 1. if view is at 0/0 and scrolling right/down, then the scroll backward button + // becomes visible, which moves the viewport right/down by the width/height of + // the button --> add button width/height to new view position so that + // tabs seems to stay in place at screen + // 2. if scrolling left/up to the beginning, then the scroll backward button + // becomes hidden, which moves the viewport left/up by the width/height of + // the button --> set new view position to 0/0 so that + // tabs seems to stay in place at screen + if( horizontal ) { + // + if( viewPosition.x == 0 && x > 0 ) + x += scrollBackwardButtonPrefSize.width; + else if( amount < 0 && x <= scrollBackwardButtonPrefSize.width ) + x = 0; + } else { + if( viewPosition.y == 0 && y > 0 ) + y += scrollBackwardButtonPrefSize.height; + else if( amount < 0 && y <= scrollBackwardButtonPrefSize.height ) + y = 0; + } } + // limit new view position + if( horizontal ) + x = Math.min( Math.max( x, 0 ), viewSize.width - tabViewport.getWidth() ); + else + y = Math.min( Math.max( y, 0 ), viewSize.height - tabViewport.getHeight() ); + // check whether view position has changed Point newViewPosition = new Point( x, y ); if( newViewPosition.equals( viewPosition ) ) return; // update view position - if( preciseWheelRotation != 0 && - preciseWheelRotation != e.getWheelRotation() ) - { + if( isPreciseWheel ) { // do not use animation for precise scrolling (e.g. with trackpad) // stop running animation (if any) @@ -2896,6 +2943,8 @@ public class FlatTabbedPaneUI moreTabsButton.setVisible( moreTabsButtonVisible ); backwardButton.setVisible( backwardButtonVisible ); forwardButton.setVisible( forwardButtonVisible ); + + scrollBackwardButtonPrefSize = backwardButton.getPreferredSize(); } } }