diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b5934f2..54e2813c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,11 @@ FlatLaf Change Log - Skip components with empty input map (e.g. `JLabel`) when using arrow keys to navigate in focusable buttons (if UI value `ToolBar.focusableButtons` is `true`). + - Support arrow-keys-only navigation within focusable buttons of toolbar (if + UI value `ToolBar.focusableButtons` is `true`): + - arrow keys move focus within toolbar + - tab-key moves focus out of toolbar + - if moving focus into the toolbar, focus recently focused toolbar button - Added more color functions to class `ColorFunctions` for easy use in applications: `lighten()`, `darken()`, `saturate()`, `desaturate()`, `spin()`, `tint()`, `shade()` and `luma()`. diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarUI.java index 87f7dc72..456c7cf3 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarUI.java @@ -18,6 +18,8 @@ package com.formdev.flatlaf.ui; import java.awt.Color; import java.awt.Component; +import java.awt.Container; +import java.awt.FocusTraversalPolicy; import java.awt.Insets; import java.awt.event.ContainerEvent; import java.awt.event.ContainerListener; @@ -26,6 +28,7 @@ import java.util.Map; import javax.swing.AbstractButton; import javax.swing.InputMap; import javax.swing.JComponent; +import javax.swing.LayoutFocusTraversalPolicy; import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.plaf.ComponentUI; @@ -52,6 +55,7 @@ import com.formdev.flatlaf.util.LoggingFacade; * * * @uiDefault ToolBar.focusableButtons boolean + * @uiDefault ToolBar.arrowKeysOnlyNavigation boolean * @uiDefault ToolBar.floatable boolean * * @@ -65,13 +69,14 @@ public class FlatToolBarUI extends BasicToolBarUI implements StyleableUI { - /** @since 1.4 */ - @Styleable protected boolean focusableButtons; + /** @since 1.4 */ @Styleable protected boolean focusableButtons; + /** @since 2 */ @Styleable protected boolean arrowKeysOnlyNavigation; // for FlatToolBarBorder @Styleable protected Insets borderMargins; @Styleable protected Color gripColor; + private FocusTraversalPolicy focusTraversalPolicy; private Boolean oldFloatable; private Map oldStyleValues; @@ -83,6 +88,8 @@ public class FlatToolBarUI public void installUI( JComponent c ) { super.installUI( c ); + installFocusTraversalPolicy(); + installStyle(); // disable focusable state of buttons (when switching from another Laf) @@ -100,6 +107,8 @@ public class FlatToolBarUI if( !focusableButtons ) setButtonsFocusable( true ); + uninstallFocusTraversalPolicy(); + oldStyleValues = null; } @@ -108,6 +117,7 @@ public class FlatToolBarUI super.installDefaults(); focusableButtons = UIManager.getBoolean( "ToolBar.focusableButtons" ); + arrowKeysOnlyNavigation = UIManager.getBoolean( "ToolBar.arrowKeysOnlyNavigation" ); // floatable if( !UIManager.getBoolean( "ToolBar.floatable" ) ) { @@ -165,11 +175,18 @@ public class FlatToolBarUI /** @since 2 */ protected void applyStyle( Object style ) { boolean oldFocusableButtons = focusableButtons; + boolean oldArrowKeysOnlyNavigation = arrowKeysOnlyNavigation; oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); if( focusableButtons != oldFocusableButtons ) setButtonsFocusable( focusableButtons ); + if( arrowKeysOnlyNavigation != oldArrowKeysOnlyNavigation || focusableButtons != oldFocusableButtons ) { + if( arrowKeysOnlyNavigation ) + installFocusTraversalPolicy(); + else + uninstallFocusTraversalPolicy(); + } } /** @since 2 */ @@ -194,6 +211,32 @@ public class FlatToolBarUI c.setFocusable( focusable ); } + /** @since 2 */ + protected void installFocusTraversalPolicy() { + if( !arrowKeysOnlyNavigation || !focusableButtons || toolBar.getFocusTraversalPolicy() != null ) + return; + + focusTraversalPolicy = createFocusTraversalPolicy(); + if( focusTraversalPolicy != null ) { + toolBar.setFocusTraversalPolicy( focusTraversalPolicy ); + toolBar.setFocusTraversalPolicyProvider( true ); + } + } + + /** @since 2 */ + protected void uninstallFocusTraversalPolicy() { + if( focusTraversalPolicy != null && toolBar.getFocusTraversalPolicy() == focusTraversalPolicy ) { + toolBar.setFocusTraversalPolicy( null ); + toolBar.setFocusTraversalPolicyProvider( false ); + } + focusTraversalPolicy = null; + } + + /** @since 2 */ + protected FocusTraversalPolicy createFocusTraversalPolicy() { + return new FlatToolBarFocusTraversalPolicy(); + } + /** * Does the same as super.navigateFocusedComp() with the exception that components * with empty input map (e.g. JLabel) are skipped. @@ -262,4 +305,51 @@ public class FlatToolBarUI super.setOrientation( orientation ); } + + //---- class FlatToolBarFocusTraversalPolicy ------------------------------ + + /** + * Focus traversal policy used for toolbar to modify traversal behaviour: + * + * + * @since 2 + */ + protected class FlatToolBarFocusTraversalPolicy + extends LayoutFocusTraversalPolicy + { + @Override + public Component getComponentAfter( Container aContainer, Component aComponent ) { + // move focus out of toolbar + return null; + } + + @Override + public Component getComponentBefore( Container aContainer, Component aComponent ) { + // move focus out of toolbar + return null; + } + + @Override + public Component getFirstComponent( Container aContainer ) { + return getRecentComponent( aContainer, true ); + } + + @Override + public Component getLastComponent( Container aContainer ) { + return getRecentComponent( aContainer, false ); + } + + private Component getRecentComponent( Container aContainer, boolean first ) { + // if moving focus into the toolbar, focus recently focused toolbar button + if( focusedCompIndex >= 0 && focusedCompIndex < toolBar.getComponentCount() ) + return toolBar.getComponent( focusedCompIndex ); + + return first + ? super.getFirstComponent( aContainer ) + : super.getLastComponent( aContainer ); + } + } } diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties index 373a4b75..7ebbb61a 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties @@ -760,8 +760,9 @@ ToggleButton.tab.focusBackground = $TabbedPane.focusColor ToolBar.border = com.formdev.flatlaf.ui.FlatToolBarBorder ToolBar.borderMargins = 2,2,2,2 ToolBar.isRollover = true -ToolBar.floatable = false ToolBar.focusableButtons = false +ToolBar.arrowKeysOnlyNavigation = true +ToolBar.floatable = false ToolBar.gripColor = @icon ToolBar.dockingBackground = darken($ToolBar.background,5%) ToolBar.dockingForeground = $Component.borderColor diff --git a/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0_202.txt b/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0_202.txt index b3f2a198..4cb864f1 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0_202.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0_202.txt @@ -1262,6 +1262,7 @@ ToggleButtonUI com.formdev.flatlaf.ui.FlatToggleButtonUI #---- ToolBar ---- +ToolBar.arrowKeysOnlyNavigation true ToolBar.background #3c3f41 HSL 204 4 25 javax.swing.plaf.ColorUIResource [UI] ToolBar.border [lazy] 2,2,2,2 false com.formdev.flatlaf.ui.FlatToolBarBorder [UI] ToolBar.borderMargins 2,2,2,2 javax.swing.plaf.InsetsUIResource [UI] diff --git a/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0_202.txt b/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0_202.txt index 2e875069..9c078938 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0_202.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0_202.txt @@ -1267,6 +1267,7 @@ ToggleButtonUI com.formdev.flatlaf.ui.FlatToggleButtonUI #---- ToolBar ---- +ToolBar.arrowKeysOnlyNavigation true ToolBar.background #f2f2f2 HSL 0 0 95 javax.swing.plaf.ColorUIResource [UI] ToolBar.border [lazy] 2,2,2,2 false com.formdev.flatlaf.ui.FlatToolBarBorder [UI] ToolBar.borderMargins 2,2,2,2 javax.swing.plaf.InsetsUIResource [UI] diff --git a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0_202.txt b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0_202.txt index 67ff731d..d2eb2d03 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0_202.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0_202.txt @@ -1275,6 +1275,7 @@ ToggleButtonUI com.formdev.flatlaf.ui.FlatToggleButtonUI #---- ToolBar ---- +ToolBar.arrowKeysOnlyNavigation true ToolBar.background #ccffcc HSL 120 100 90 javax.swing.plaf.ColorUIResource [UI] ToolBar.border [lazy] 2,2,2,2 false com.formdev.flatlaf.ui.FlatToolBarBorder [UI] ToolBar.borderMargins 2,2,2,2 javax.swing.plaf.InsetsUIResource [UI] diff --git a/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt b/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt index c9a2c265..48b8477b 100644 --- a/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt +++ b/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt @@ -983,6 +983,7 @@ ToggleButton.toolbar.pressedBackground ToggleButton.toolbar.selectedBackground ToggleButtonUI ToolBar.ancestorInputMap +ToolBar.arrowKeysOnlyNavigation ToolBar.background ToolBar.border ToolBar.borderMargins