From 2f3427e6ad672cc5b3fe5dd958327d3f6aa8cc43 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Wed, 7 Oct 2020 13:29:15 +0200 Subject: [PATCH] TabbedPane: scroll selected tab into visible area (500ms delayed) if mouse exits scroll viewport after wheel scrolling (issue #40) --- .../formdev/flatlaf/ui/FlatTabbedPaneUI.java | 75 ++++++++++++++++++- 1 file changed, 72 insertions(+), 3 deletions(-) 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 1a4de095..ec85c592 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 @@ -46,6 +46,7 @@ import javax.swing.JComponent; import javax.swing.JTabbedPane; import javax.swing.JViewport; import javax.swing.KeyStroke; +import javax.swing.Timer; import javax.swing.UIManager; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.UIResource; @@ -220,6 +221,7 @@ public class FlatTabbedPaneUI // mouse listener of the tabbed pane would not receive events while // the mouse pointer is over the viewport tabPane.addMouseWheelListener( wheelTabScroller ); + tabPane.addMouseMotionListener( wheelTabScroller ); tabPane.addMouseListener( wheelTabScroller ); } } @@ -229,7 +231,10 @@ public class FlatTabbedPaneUI super.uninstallListeners(); if( wheelTabScroller != null ) { + wheelTabScroller.uninstall(); + tabPane.removeMouseWheelListener( wheelTabScroller ); + tabPane.removeMouseMotionListener( wheelTabScroller ); tabPane.removeMouseListener( wheelTabScroller ); wheelTabScroller = null; } @@ -571,11 +576,20 @@ public class FlatTabbedPaneUI protected class FlatWheelTabScroller extends MouseAdapter { + private boolean inViewport; + private boolean scrolled; + private Timer timer; + + protected void uninstall() { + if( timer != null ) + timer.stop(); + } + @Override public void mouseWheelMoved( MouseWheelEvent e ) { // because this listener receives mouse events for the whole tabbed pane, // we have to check whether the mouse is located over the viewport - if( tabViewport == null || !tabViewport.getBounds().contains( e.getX(), e.getY() ) ) + if( !isInViewport( e ) ) return; // compute new view position @@ -593,19 +607,74 @@ public class FlatTabbedPaneUI } // update view position - tabViewport.setViewPosition( new Point( x, y ) ); + Point newViewPosition = new Point( x, y ); + tabViewport.setViewPosition( newViewPosition ); + + if( !newViewPosition.equals( viewPosition )) + scrolled = true; updateHover( e ); } + @Override + public void mouseMoved( MouseEvent e ) { + boolean wasInViewport = inViewport; + inViewport = isInViewport( e ); + + if( inViewport != wasInViewport ) { + if( !inViewport ) + viewportExited(); + else if( timer != null ) + timer.stop(); + } + } + + @Override + public void mouseExited( MouseEvent e ) { + inViewport = false; + viewportExited(); + } + @Override public void mousePressed( MouseEvent e ) { // for the case that the tab was only partly visible before the user clicked it updateHover( e ); } - private void updateHover( MouseEvent e ) { + protected boolean isInViewport( MouseEvent e ) { + return (tabViewport != null && tabViewport.getBounds().contains( e.getX(), e.getY() ) ); + } + + protected void updateHover( MouseEvent e ) { setRolloverTab( tabForCoordinate( tabPane, e.getX(), e.getY() ) ); } + + protected void viewportExited() { + if( !scrolled ) + return; + + if( timer == null ) { + timer = new Timer( 500, e -> ensureSelectedTabVisible() ); + timer.setRepeats( false ); + } + + timer.start(); + } + + protected void ensureSelectedTabVisible() { + // check whether UI delegate was uninstalled because this method is invoked via timer + if( tabPane == null || tabViewport == null ) + return; + + if( !scrolled || tabViewport == null ) + return; + scrolled = false; + + int selectedIndex = tabPane.getSelectedIndex(); + if( selectedIndex >= 0 ) { + Rectangle tabBounds = getTabBounds( tabPane, selectedIndex ); + tabViewport.scrollRectToVisible( tabBounds ); + } + } } }