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 463ebe428..e4285b87b 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 @@ -35,11 +35,14 @@ import java.text.AttributedCharacterIterator; import java.util.Map; import javax.swing.Icon; +import javax.swing.JCheckBoxMenuItem; import javax.swing.JComponent; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; +import javax.swing.JRadioButtonMenuItem; import javax.swing.KeyStroke; +import javax.swing.MenuElement; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.plaf.basic.BasicHTML; @@ -73,6 +76,7 @@ * @uiDefault MenuItem.underlineSelectionCheckBackground Color * @uiDefault MenuItem.underlineSelectionColor Color * @uiDefault MenuItem.underlineSelectionHeight int + * @uiDefault MenuItem.checkIconSeparated boolean * * @author Karl Tauber */ @@ -103,6 +107,7 @@ public class FlatMenuItemRenderer @Styleable protected Color underlineSelectionCheckBackground = UIManager.getColor( "MenuItem.underlineSelectionCheckBackground" ); @Styleable protected Color underlineSelectionColor = UIManager.getColor( "MenuItem.underlineSelectionColor" ); @Styleable protected int underlineSelectionHeight = UIManager.getInt( "MenuItem.underlineSelectionHeight" ); + @Styleable protected boolean checkIconSeparated = UIManager.getBoolean( "MenuItem.checkIconSeparated" ); private boolean iconsShared = true; private final Font menuFont = UIManager.getFont( "Menu.font" ); @@ -195,6 +200,7 @@ protected Dimension getPreferredMenuItemSize() { int width = 0; int height = 0; boolean isTopLevelMenu = isTopLevelMenu( menuItem ); + boolean isIconSeparated = isIconSeparated( menuItem ); Rectangle viewRect = new Rectangle( 0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE ); Rectangle iconRect = new Rectangle(); @@ -237,6 +243,11 @@ protected Dimension getPreferredMenuItemSize() { height = Math.max( arrowIcon.getIconHeight(), height ); } + if( isIconSeparated ) { + // gap between check and icon + width += iconRect.width + scale( menuItem.getIconTextGap() ); + } + // add insets Insets insets = menuItem.getInsets(); width += insets.left + insets.right; @@ -251,10 +262,11 @@ protected Dimension getPreferredMenuItemSize() { return new Dimension( width, height ); } - private void layout( Rectangle viewRect, Rectangle iconRect, Rectangle textRect, - Rectangle accelRect, Rectangle arrowRect, Rectangle labelRect ) + private void layout( Rectangle viewRect, Rectangle checkRect, Rectangle iconRect, + Rectangle textRect, Rectangle accelRect, Rectangle arrowRect, Rectangle labelRect ) { boolean isTopLevelMenu = isTopLevelMenu( menuItem ); + boolean isIconSeparated = isIconSeparated( menuItem ); // layout arrow boolean showArrowIcon = (arrowIcon != null && (!isTopLevelMenu || isInVerticalMenuBar( menuItem ))); @@ -311,6 +323,16 @@ private void layout( Rectangle viewRect, Rectangle iconRect, Rectangle textRect, menuItem.getVerticalAlignment(), menuItem.getHorizontalAlignment(), menuItem.getVerticalTextPosition(), menuItem.getHorizontalTextPosition(), labelRect, iconRect, textRect, scale( menuItem.getIconTextGap() ) ); + + checkRect.setBounds( iconRect ); + + if( isIconSeparated ) { + int width = checkRect.width + scale( menuItem.getIconTextGap() ); + iconRect.x += width; + textRect.x += width; + labelRect.x += width; + textRect.width -= width; + } } private static int centerOffset( int wh1, int wh2 ) { @@ -329,15 +351,17 @@ protected void paintMenuItem( Graphics g, Color selectionBackground, Color selec viewRect.width -= (insets.left + insets.right); viewRect.height -= (insets.top + insets.bottom); + Rectangle checkRect = new Rectangle(); Rectangle iconRect = new Rectangle(); Rectangle textRect = new Rectangle(); Rectangle accelRect = new Rectangle(); Rectangle arrowRect = new Rectangle(); Rectangle labelRect = new Rectangle(); - layout( viewRect, iconRect, textRect, accelRect, arrowRect, labelRect ); + layout( viewRect, checkRect, iconRect, textRect, accelRect, arrowRect, labelRect ); /*debug + g.setColor( Color.pink ); g.drawRect( checkRect.x, checkRect.y, checkRect.width - 1, checkRect.height - 1 ); g.setColor( Color.green ); g.drawRect( viewRect.x, viewRect.y, viewRect.width - 1, viewRect.height - 1 ); g.setColor( Color.red ); g.drawRect( labelRect.x, labelRect.y, labelRect.width - 1, labelRect.height - 1 ); g.setColor( Color.blue ); g.drawRect( iconRect.x, iconRect.y, iconRect.width - 1, iconRect.height - 1 ); @@ -356,7 +380,19 @@ protected void paintMenuItem( Graphics g, Color selectionBackground, Color selec else paintSelection( g, selectionBackground, selectionInsets, selectionArc ); } - paintIcon( g, iconRect, getIconForPainting(), underlineSelection ? underlineSelectionCheckBackground : checkBackground, selectionBackground ); + + if( isIconSeparated( menuItem ) ) { + if( menuItem instanceof JCheckBoxMenuItem ) { + paintIcon( g, checkRect, UIManager.getIcon( "CheckBox.icon" ) ); + } else if( menuItem instanceof JRadioButtonMenuItem ) { + paintIcon( g, checkRect, UIManager.getIcon( "RadioButton.icon" ) ); + } + + paintIcon( g, iconRect, getIconForPainting(false) ); + } else { + paintIcon( g, iconRect, getIconForPainting(true), underlineSelection ? underlineSelectionCheckBackground : checkBackground, selectionBackground ); + } + paintText( g, textRect, menuItem.getText(), selectionForeground, disabledForeground ); paintAccelerator( g, accelRect, getAcceleratorText(), acceleratorForeground, acceleratorSelectionForeground, disabledForeground ); if( arrowIcon != null && (!isTopLevelMenu( menuItem ) || isInVerticalMenuBar( menuItem )) ) @@ -429,6 +465,10 @@ protected void paintIcon( Graphics g, Rectangle iconRect, Icon icon, Color check paintIcon( g, menuItem, icon, iconRect ); } + protected void paintIcon( Graphics g, Rectangle iconRect, Icon icon ) { + paintIcon( g, menuItem, icon, iconRect ); + } + protected void paintText( Graphics g, Rectangle textRect, String text, Color selectionForeground, Color disabledForeground ) { View htmlView = (View) menuItem.getClientProperty( BasicHTML.propertyKey ); if( htmlView != null ) { @@ -537,6 +577,24 @@ protected boolean isUnderlineSelection() { return "underline".equals( UIManager.getString( "MenuItem.selectionType" ) ); } + protected boolean isIconSeparated( JMenuItem item ) { + if( !checkIconSeparated ) + return false; + + Container parent = item.getParent(); + if( !(parent instanceof MenuElement) ) + return false; + + for( MenuElement element : ((MenuElement) parent).getSubElements() ) { + Component c = element.getComponent(); + if( c instanceof JCheckBoxMenuItem && ((JCheckBoxMenuItem) c).getIcon() != null ) + return true; + if( c instanceof JRadioButtonMenuItem && ((JRadioButtonMenuItem) c).getIcon() != null ) + return true; + } + return false; + } + private Font getTopLevelFont() { Font font = menuItem.getFont(); // menu item parent may be null if JMenu.isTopLevelMenu() is overridden @@ -546,10 +604,10 @@ private Font getTopLevelFont() { : menuItem.getParent().getFont(); } - private Icon getIconForPainting() { + private Icon getIconForPainting(boolean canUseCheckIcon) { Icon icon = menuItem.getIcon(); - if( icon == null && checkIcon != null && !isTopLevelMenu( menuItem ) ) + if( icon == null && canUseCheckIcon && checkIcon != null && !isTopLevelMenu( menuItem ) ) return checkIcon; if( icon == 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 b0cb557cb..16d5d7453 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties @@ -447,6 +447,7 @@ MenuItem.verticallyAlignText = true MenuItem.background = @menuBackground MenuItem.checkBackground = @menuCheckBackground MenuItem.checkMargins = 2,2,2,2 +MenuItem.checkIconSeparated = false MenuItem.minimumWidth = 72 MenuItem.minimumIconSize = 16,16 MenuItem.iconTextGap = 6 diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java index ccb0073d2..6b6601a8c 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java @@ -635,6 +635,11 @@ private boolean supportsFlatLafWindowDecorations() { return FlatLaf.supportsNativeWindowDecorations() || SystemInfo.isLinux; } + private void showCheckMenuIconsSeparately() { + UIManager.put( "MenuItem.checkIconSeparated", showMenuCheckIconsSeparately.isSelected() ); + FlatLaf.updateUI(); + } + private void initComponents() { // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents menuBar = new JMenuBar(); @@ -667,6 +672,10 @@ private void initComponents() { scrollingPopupMenu = new JMenu(); JMenuItem menuItem2 = new JMenuItem(); htmlMenuItem = new JMenuItem(); + JCheckBoxMenuItem checkBoxMenuItem2 = new JCheckBoxMenuItem(); + JCheckBoxMenuItem checkBoxMenuItem3 = new JCheckBoxMenuItem(); + JRadioButtonMenuItem radioButtonMenuItem4 = new JRadioButtonMenuItem(); + JRadioButtonMenuItem radioButtonMenuItem5 = new JRadioButtonMenuItem(); JRadioButtonMenuItem radioButtonMenuItem1 = new JRadioButtonMenuItem(); JRadioButtonMenuItem radioButtonMenuItem2 = new JRadioButtonMenuItem(); JRadioButtonMenuItem radioButtonMenuItem3 = new JRadioButtonMenuItem(); @@ -686,6 +695,7 @@ private void initComponents() { underlineMenuSelectionMenuItem = new JCheckBoxMenuItem(); alwaysShowMnemonicsMenuItem = new JCheckBoxMenuItem(); animatedLafChangeMenuItem = new JCheckBoxMenuItem(); + showMenuCheckIconsSeparately = new JCheckBoxMenuItem(); JMenuItem showHintsMenuItem = new JMenuItem(); JMenuItem showUIDefaultsInspectorMenuItem = new JMenuItem(); JMenu helpMenu = new JMenu(); @@ -915,6 +925,28 @@ private void initComponents() { //---- htmlMenuItem ---- htmlMenuItem.setText("some HTML text"); viewMenu.add(htmlMenuItem); + + //---- checkBoxMenuItem2 ---- + checkBoxMenuItem2.setText("Check Box"); + checkBoxMenuItem2.setSelected(true); + viewMenu.add(checkBoxMenuItem2); + + //---- checkBoxMenuItem3 ---- + checkBoxMenuItem3.setText("Check Box with Icon"); + checkBoxMenuItem3.setSelected(true); + checkBoxMenuItem3.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/show.svg")); + viewMenu.add(checkBoxMenuItem3); + + //---- radioButtonMenuItem4 ---- + radioButtonMenuItem4.setText("Radio Button"); + radioButtonMenuItem4.setSelected(true); + viewMenu.add(radioButtonMenuItem4); + + //---- radioButtonMenuItem5 ---- + radioButtonMenuItem5.setText("Radio Button with Icon"); + radioButtonMenuItem5.setSelected(true); + radioButtonMenuItem5.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/show.svg")); + viewMenu.add(radioButtonMenuItem5); viewMenu.addSeparator(); //---- radioButtonMenuItem1 ---- @@ -1026,6 +1058,11 @@ private void initComponents() { animatedLafChangeMenuItem.addActionListener(e -> animatedLafChangeChanged()); optionsMenu.add(animatedLafChangeMenuItem); + //---- showMenuCheckIconsSeparately ---- + showMenuCheckIconsSeparately.setText("Show check menu icons separately"); + showMenuCheckIconsSeparately.addActionListener(e -> showCheckMenuIconsSeparately()); + optionsMenu.add(showMenuCheckIconsSeparately); + //---- showHintsMenuItem ---- showHintsMenuItem.setText("Show hints"); showHintsMenuItem.addActionListener(e -> showHintsChanged()); @@ -1239,6 +1276,7 @@ private void unsupported( JCheckBoxMenuItem menuItem ) { private JCheckBoxMenuItem underlineMenuSelectionMenuItem; private JCheckBoxMenuItem alwaysShowMnemonicsMenuItem; private JCheckBoxMenuItem animatedLafChangeMenuItem; + private JCheckBoxMenuItem showMenuCheckIconsSeparately; private JMenuItem aboutMenuItem; private JToolBar toolBar; private JTabbedPane tabbedPane; diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd index 0db8f3fba..3c479ab99 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd @@ -357,6 +357,28 @@ new FormModel { "JavaCodeGenerator.variableLocal": false } } ) + add( new FormComponent( "javax.swing.JCheckBoxMenuItem" ) { + name: "checkBoxMenuItem2" + "text": "Check Box" + "selected": true + } ) + add( new FormComponent( "javax.swing.JCheckBoxMenuItem" ) { + name: "checkBoxMenuItem3" + "text": "Check Box with Icon" + "selected": true + "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/show.svg" ) + } ) + add( new FormComponent( "javax.swing.JRadioButtonMenuItem" ) { + name: "radioButtonMenuItem4" + "text": "Radio Button" + "selected": true + } ) + add( new FormComponent( "javax.swing.JRadioButtonMenuItem" ) { + name: "radioButtonMenuItem5" + "text": "Radio Button with Icon" + "selected": true + "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/show.svg" ) + } ) add( new FormComponent( "javax.swing.JPopupMenu$Separator" ) { name: "separator8" } ) @@ -496,6 +518,14 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "animatedLafChangeChanged", false ) ) } ) + add( new FormComponent( "javax.swing.JCheckBoxMenuItem" ) { + name: "showMenuCheckIconsSeparately" + "text": "Show check menu icons separately" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "showCheckMenuIconsSeparately", false ) ) + } ) add( new FormComponent( "javax.swing.JMenuItem" ) { name: "showHintsMenuItem" "text": "Show hints" 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 d4c750f74..c5b4f32b8 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 @@ -550,6 +550,7 @@ MenuItem.border MenuItem.borderPainted MenuItem.checkBackground MenuItem.checkMargins +MenuItem.checkIconSeparated MenuItem.disabledForeground MenuItem.font MenuItem.foreground