From 023e356057b8e4ca573dae81efcb634e74a68985 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Wed, 8 Dec 2021 11:45:17 +0100 Subject: [PATCH] MenuItem: vertically align text if icons have different widths (issue #437) --- CHANGELOG.md | 4 +- .../flatlaf/ui/FlatCheckBoxMenuItemUI.java | 1 + .../flatlaf/ui/FlatMenuItemRenderer.java | 49 ++++++++++++++++++- .../formdev/flatlaf/ui/FlatMenuItemUI.java | 1 + .../com/formdev/flatlaf/ui/FlatMenuUI.java | 1 + .../formdev/flatlaf/ui/FlatPopupMenuUI.java | 32 ++++++++++++ .../flatlaf/ui/FlatRadioButtonMenuItemUI.java | 1 + .../com/formdev/flatlaf/FlatLaf.properties | 1 + .../flatlaf/ui/TestFlatStyleableInfo.java | 1 + .../formdev/flatlaf/ui/TestFlatStyling.java | 1 + .../dumps/uidefaults/FlatDarkLaf_1.8.0.txt | 1 + .../dumps/uidefaults/FlatLightLaf_1.8.0.txt | 1 + .../dumps/uidefaults/FlatTestLaf_1.8.0.txt | 1 + .../flatlaf/testing/FlatMenusTest.java | 31 ++++++++++++ .../formdev/flatlaf/testing/FlatMenusTest.jfd | 28 +++++++++++ .../flatlaf/themeeditor/FlatLafUIKeys.txt | 1 + 16 files changed, 152 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b65bf57f..4882c10a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,7 +61,9 @@ FlatLaf Change Log - Slider: Support specifying width of thumb border (see UI value `Slider.thumbBorderWidth`). - TabbedPane: Optionally paint selected tab as card. (PR #343) -- MenuItem: Paint the selected icon when the item is selected. (PR #415) +- MenuItem: + - Paint the selected icon when the item is selected. (PR #415) + - Vertically align text if icons have different widths. (issue #437) - 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/FlatCheckBoxMenuItemUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatCheckBoxMenuItemUI.java index 8c0ab140..e44147cb 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatCheckBoxMenuItemUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatCheckBoxMenuItemUI.java @@ -89,6 +89,7 @@ public class FlatCheckBoxMenuItemUI protected void uninstallDefaults() { super.uninstallDefaults(); + FlatMenuItemRenderer.clearClientProperties( menuItem.getParent() ); renderer = null; oldStyleValues = null; } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemRenderer.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemRenderer.java index 6c74fdef..37fb87ed 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemRenderer.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemRenderer.java @@ -19,6 +19,7 @@ package com.formdev.flatlaf.ui; import static com.formdev.flatlaf.util.UIScale.scale; import java.awt.Color; import java.awt.Component; +import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; @@ -32,6 +33,7 @@ import java.awt.event.KeyEvent; import java.text.AttributedCharacterIterator; import java.util.Map; import javax.swing.Icon; +import javax.swing.JComponent; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.KeyStroke; @@ -39,6 +41,7 @@ import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.plaf.basic.BasicHTML; import javax.swing.text.View; +import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.icons.FlatCheckBoxMenuItemIcon; import com.formdev.flatlaf.icons.FlatMenuArrowIcon; @@ -52,6 +55,7 @@ import com.formdev.flatlaf.util.SystemInfo; /** * Renderer for menu items. * + * @uiDefault MenuItem.verticallyAlignText boolean * @uiDefault MenuItem.minimumWidth int * @uiDefault MenuItem.minimumIconSize Dimension * @uiDefault MenuItem.textAcceleratorGap int @@ -67,12 +71,15 @@ import com.formdev.flatlaf.util.SystemInfo; */ public class FlatMenuItemRenderer { + private static final String KEY_MAX_ICONS_WIDTH = "FlatLaf.internal.FlatMenuItemRenderer.maxIconWidth"; + protected final JMenuItem menuItem; protected Icon checkIcon; protected Icon arrowIcon; protected final Font acceleratorFont; protected final String acceleratorDelimiter; + /** @since 2 */ @Styleable protected boolean verticallyAlignText = FlatUIUtils.getUIBoolean( "MenuItem.verticallyAlignText", true ); @Styleable protected int minimumWidth = UIManager.getInt( "MenuItem.minimumWidth" ); @Styleable protected Dimension minimumIconSize; @Styleable protected int textAcceleratorGap = FlatUIUtils.getUIInt( "MenuItem.textAcceleratorGap", 28 ); @@ -405,11 +412,10 @@ debug*/ return; // center because the real icon may be smaller than dimension in iconRect - int x = iconRect.x + centerOffset( iconRect.width, icon.getIconWidth() ); int y = iconRect.y + centerOffset( iconRect.height, icon.getIconHeight() ); // paint - icon.paintIcon( menuItem, g, x, y ); + icon.paintIcon( menuItem, g, iconRect.x, y ); } protected static void paintText( Graphics g, JMenuItem menuItem, @@ -570,6 +576,44 @@ debug*/ shiftGlyph = 0x21E7, commandGlyph = 0x2318; + /** + * Calculates the maximum width of all menu item icons in the popup. + */ + private int getMaxIconsWidth() { + if( !verticallyAlignText ) + return 0; + + Container parent = menuItem.getParent(); + if( !(parent instanceof JComponent) ) + return 0; + + int maxWidth = FlatClientProperties.clientPropertyInt( (JComponent) parent, KEY_MAX_ICONS_WIDTH, -1 ); + if( maxWidth >= 0 ) + return maxWidth; + + maxWidth = 0; + + for( Component c : parent.getComponents() ) { + if( !(c instanceof JMenuItem) ) + continue; + + Icon icon = ((JMenuItem)c).getIcon(); + if( icon != null ) + maxWidth = Math.max( maxWidth, icon.getIconWidth() ); + } + + ((JComponent)parent).putClientProperty( KEY_MAX_ICONS_WIDTH, maxWidth ); + return maxWidth; + } + + static void clearClientProperties( Component c ) { + if( !(c instanceof JComponent) ) + return; + + JComponent jc = (JComponent) c; + jc.putClientProperty( FlatMenuItemRenderer.KEY_MAX_ICONS_WIDTH, null ); + } + //---- class MinSizeIcon -------------------------------------------------- private class MinSizeIcon @@ -584,6 +628,7 @@ debug*/ @Override public int getIconWidth() { int iconWidth = (delegate != null) ? delegate.getIconWidth() : 0; + iconWidth = Math.max( iconWidth, getMaxIconsWidth() ); return Math.max( iconWidth, scale( minimumIconSize.width ) ); } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemUI.java index 39a64942..fdd15bac 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemUI.java @@ -89,6 +89,7 @@ public class FlatMenuItemUI protected void uninstallDefaults() { super.uninstallDefaults(); + FlatMenuItemRenderer.clearClientProperties( menuItem.getParent() ); renderer = null; oldStyleValues = null; } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuUI.java index 37d819cb..be04f393 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuUI.java @@ -111,6 +111,7 @@ public class FlatMenuUI protected void uninstallDefaults() { super.uninstallDefaults(); + FlatMenuItemRenderer.clearClientProperties( menuItem.getParent() ); renderer = null; oldStyleValues = null; } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupMenuUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupMenuUI.java index cb3d6169..365306ed 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupMenuUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupMenuUI.java @@ -16,12 +16,18 @@ package com.formdev.flatlaf.ui; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.LayoutManager; import java.beans.PropertyChangeListener; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; +import javax.swing.BoxLayout; import javax.swing.JComponent; import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicPopupMenuUI; +import javax.swing.plaf.basic.DefaultMenuLayout; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.util.LoggingFacade; @@ -64,6 +70,15 @@ public class FlatPopupMenuUI borderShared = null; } + @Override + public void installDefaults() { + super.installDefaults(); + + LayoutManager layout = popupMenu.getLayout(); + if( layout == null || layout instanceof UIResource ) + popupMenu.setLayout( new FlatMenuLayout( popupMenu, BoxLayout.Y_AXIS ) ); + } + @Override protected void installListeners() { super.installListeners(); @@ -106,4 +121,21 @@ public class FlatPopupMenuUI public Map> getStyleableInfos( JComponent c ) { return FlatStylingSupport.getAnnotatedStyleableInfos( this, popupMenu.getBorder() ); } + + //---- class FlatMenuLayout ----------------------------------------------- + + protected static class FlatMenuLayout + extends DefaultMenuLayout + { + public FlatMenuLayout( Container target, int axis ) { + super( target, axis ); + } + + @Override + public Dimension preferredLayoutSize( Container target ) { + FlatMenuItemRenderer.clearClientProperties( target ); + + return super.preferredLayoutSize( target ); + } + } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonMenuItemUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonMenuItemUI.java index efb99f58..47f7ba06 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonMenuItemUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonMenuItemUI.java @@ -89,6 +89,7 @@ public class FlatRadioButtonMenuItemUI protected void uninstallDefaults() { super.uninstallDefaults(); + FlatMenuItemRenderer.clearClientProperties( menuItem.getParent() ); renderer = null; oldStyleValues = null; } 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 b8e79354..33340e78 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties @@ -430,6 +430,7 @@ MenuItem.checkIcon = null MenuItem.margin = @menuItemMargin MenuItem.opaque = false MenuItem.borderPainted = true +MenuItem.verticallyAlignText = true MenuItem.background = @menuBackground MenuItem.checkBackground = @menuCheckBackground MenuItem.checkMargins = 2,2,2,2 diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableInfo.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableInfo.java index 4b4c1665..12a1bd72 100644 --- a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableInfo.java +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableInfo.java @@ -346,6 +346,7 @@ public class TestFlatStyleableInfo private void menuItemRenderer( Map> expected ) { expectedMap( expected, + "verticallyAlignText", boolean.class, "minimumWidth", int.class, "minimumIconSize", Dimension.class, "textAcceleratorGap", int.class, diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyling.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyling.java index 7ea73961..3d32ced3 100644 --- a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyling.java +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyling.java @@ -495,6 +495,7 @@ public class TestFlatStyling } private void menuItemRenderer( Consumer applyStyle ) { + applyStyle.accept( "verticallyAlignText: false" ); applyStyle.accept( "minimumWidth: 10" ); applyStyle.accept( "minimumIconSize: 16,16" ); applyStyle.accept( "textAcceleratorGap: 28" ); diff --git a/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt index dab44ad6..5d305543 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt @@ -628,6 +628,7 @@ MenuItem.underlineSelectionBackground #484c4f HSL 206 5 30 com.formdev.fl MenuItem.underlineSelectionCheckBackground #3c588b HSL 219 40 39 com.formdev.flatlaf.util.DerivedColor [UI] darken(10%) MenuItem.underlineSelectionColor #4c87c8 HSL 211 53 54 javax.swing.plaf.ColorUIResource [UI] MenuItem.underlineSelectionHeight 3 +MenuItem.verticallyAlignText true MenuItemUI com.formdev.flatlaf.ui.FlatMenuItemUI diff --git a/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt index df2eb746..d9efdd96 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt @@ -633,6 +633,7 @@ MenuItem.underlineSelectionBackground #e6e6e6 HSL 0 0 90 com.formdev.fl MenuItem.underlineSelectionCheckBackground #bfd9f2 HSL 209 66 85 com.formdev.flatlaf.util.DerivedColor [UI] lighten(40%) MenuItem.underlineSelectionColor #3c83c5 HSL 209 54 50 javax.swing.plaf.ColorUIResource [UI] MenuItem.underlineSelectionHeight 3 +MenuItem.verticallyAlignText true MenuItemUI com.formdev.flatlaf.ui.FlatMenuItemUI diff --git a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt index ac9ff075..43836b5f 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt @@ -638,6 +638,7 @@ MenuItem.underlineSelectionBackground #e6e6e6 HSL 0 0 90 javax.swing.pl MenuItem.underlineSelectionCheckBackground #ccccff HSL 240 100 90 javax.swing.plaf.ColorUIResource [UI] MenuItem.underlineSelectionColor #ff0000 HSL 0 100 50 javax.swing.plaf.ColorUIResource [UI] MenuItem.underlineSelectionHeight 3 +MenuItem.verticallyAlignText true MenuItemUI com.formdev.flatlaf.ui.FlatMenuItemUI diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMenusTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMenusTest.java index 55d2d9ec..399b41a3 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMenusTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMenusTest.java @@ -165,6 +165,11 @@ public class FlatMenusTest JMenu menu12 = new JMenu(); JMenuItem menuItem41 = new JMenuItem(); JMenuItem menuItem42 = new JMenuItem(); + JMenuItem menuItem43 = new JMenuItem(); + JMenuItem menuItem44 = new JMenuItem(); + JMenuItem menuItem45 = new JMenuItem(); + JMenuItem menuItem46 = new JMenuItem(); + JMenuItem menuItem47 = new JMenuItem(); menuBar2 = new JMenuBar(); JMenu menu8 = new JMenu(); FlatMenusTest.LargerMenuItem menuItem13 = new FlatMenusTest.LargerMenuItem(); @@ -397,6 +402,32 @@ public class FlatMenusTest menuItem42.setDisabledIcon(new ImageIcon(getClass().getResource("/com/formdev/flatlaf/testing/disabled_icons_test/intellij-menu-paste.png"))); menuItem42.setEnabled(false); menu12.add(menuItem42); + menu12.addSeparator(); + + //---- menuItem43 ---- + menuItem43.setText("text"); + menuItem43.setIcon(new ImageIcon(getClass().getResource("/com/formdev/flatlaf/testing/test16.png"))); + menu12.add(menuItem43); + + //---- menuItem44 ---- + menuItem44.setText("text"); + menuItem44.setIcon(new ImageIcon(getClass().getResource("/com/formdev/flatlaf/testing/test24.png"))); + menu12.add(menuItem44); + + //---- menuItem45 ---- + menuItem45.setText("text"); + menuItem45.setIcon(new ImageIcon(getClass().getResource("/com/formdev/flatlaf/testing/test32.png"))); + menu12.add(menuItem45); + + //---- menuItem46 ---- + menuItem46.setText("text"); + menuItem46.setIcon(new ImageIcon(getClass().getResource("/com/formdev/flatlaf/testing/test48.png"))); + menu12.add(menuItem46); + + //---- menuItem47 ---- + menuItem47.setText("text"); + menuItem47.setIcon(new ImageIcon(getClass().getResource("/com/formdev/flatlaf/testing/test64.png"))); + menu12.add(menuItem47); } menuBar1.add(menu12); } diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMenusTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMenusTest.jfd index 1bcac756..98071c78 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMenusTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMenusTest.jfd @@ -165,6 +165,34 @@ new FormModel { "disabledIcon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/testing/disabled_icons_test/intellij-menu-paste.png" ) "enabled": false } ) + add( new FormComponent( "javax.swing.JPopupMenu$Separator" ) { + name: "separator6" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem43" + "text": "text" + "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/testing/test16.png" ) + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem44" + "text": "text" + "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/testing/test24.png" ) + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem45" + "text": "text" + "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/testing/test32.png" ) + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem46" + "text": "text" + "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/testing/test48.png" ) + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "menuItem47" + "text": "text" + "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/testing/test64.png" ) + } ) } ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 1 0 2 1,growx" 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 0f7e01c0..be008dcc 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 @@ -495,6 +495,7 @@ MenuItem.underlineSelectionBackground MenuItem.underlineSelectionCheckBackground MenuItem.underlineSelectionColor MenuItem.underlineSelectionHeight +MenuItem.verticallyAlignText MenuItemUI MenuUI MonthViewUI