From fcbb3aeed12b71dd8c8b1fb40e6f3ea97022bce0 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sun, 26 Apr 2020 10:13:52 +0200 Subject: [PATCH] Menus: new menu item layout and renderer - stable left margin (always space for one icon) - right aligned accelerators - larger gap between text and accelerator current limitations: - no HTML text support - text not vertically aligned with other menu items if icons have different sizes - vertical/horizontal alignment/textPosition properties are ignored (issues #3 and #54) --- .../flatlaf/ui/FlatCheckBoxMenuItemUI.java | 46 ++- .../flatlaf/ui/FlatMenuItemRenderer.java | 329 ++++++++++++++++++ .../formdev/flatlaf/ui/FlatMenuItemUI.java | 64 ++-- .../com/formdev/flatlaf/ui/FlatMenuUI.java | 72 ++-- .../flatlaf/ui/FlatRadioButtonMenuItemUI.java | 46 ++- .../com/formdev/flatlaf/FlatLaf.properties | 6 +- 6 files changed, 460 insertions(+), 103 deletions(-) create mode 100644 flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemRenderer.java 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 f1cb3767d..9d7b53c46 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 @@ -16,12 +16,10 @@ package com.formdev.flatlaf.ui; -import static com.formdev.flatlaf.util.UIScale.scale; +import java.awt.Dimension; import java.awt.Graphics; -import java.awt.Rectangle; -import java.beans.PropertyChangeListener; +import javax.swing.Icon; import javax.swing.JComponent; -import javax.swing.JMenuItem; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicCheckBoxMenuItemUI; @@ -48,11 +46,20 @@ * @uiDefault CheckBoxMenuItem.opaque boolean * @uiDefault CheckBoxMenuItem.evenHeight boolean * + * + * + * @uiDefault MenuItem.minimumIconSize Dimension + * @uiDefault MenuItem.textAcceleratorGap int + * @uiDefault MenuItem.acceleratorArrowGap int + * @uiDefault MenuItem.textArrowGap int + * * @author Karl Tauber */ public class FlatCheckBoxMenuItemUI extends BasicCheckBoxMenuItemUI { + private FlatMenuItemRenderer renderer; + public static ComponentUI createUI( JComponent c ) { return new FlatCheckBoxMenuItemUI(); } @@ -61,25 +68,28 @@ public static ComponentUI createUI( JComponent c ) { protected void installDefaults() { super.installDefaults(); - // scale - defaultTextIconGap = scale( defaultTextIconGap ); + renderer = createRenderer(); + } + + @Override + protected void uninstallDefaults() { + super.uninstallDefaults(); + + renderer = null; + } + + protected FlatMenuItemRenderer createRenderer() { + return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter ); } - /** - * Scale defaultTextIconGap again if iconTextGap property has changed. - */ @Override - protected PropertyChangeListener createPropertyChangeListener( JComponent c ) { - PropertyChangeListener superListener = super.createPropertyChangeListener( c ); - return e -> { - superListener.propertyChange( e ); - if( e.getPropertyName() == "iconTextGap" ) - defaultTextIconGap = scale( defaultTextIconGap ); - }; + protected Dimension getPreferredMenuItemSize( JComponent c, Icon checkIcon, Icon arrowIcon, int defaultTextIconGap ) { + return renderer.getPreferredMenuItemSize(); } @Override - protected void paintText( Graphics g, JMenuItem menuItem, Rectangle textRect, String text ) { - FlatMenuItemUI.paintText( g, menuItem, textRect, text, disabledForeground, selectionForeground ); + public void paint( Graphics g, JComponent c ) { + renderer.paintMenuItem( g, selectionBackground, selectionForeground, disabledForeground, + acceleratorForeground, acceleratorSelectionForeground ); } } 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 new file mode 100644 index 000000000..d8ce988ce --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemRenderer.java @@ -0,0 +1,329 @@ +/* + * Copyright 2020 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.formdev.flatlaf.ui; + +import static com.formdev.flatlaf.util.UIScale.scale; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Insets; +import java.awt.Rectangle; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import javax.swing.Icon; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.KeyStroke; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import com.formdev.flatlaf.FlatLaf; + +/** + * Renderer for menu items. + * + * @author Karl Tauber + */ +public class FlatMenuItemRenderer +{ + protected final JMenuItem menuItem; + protected final Icon checkIcon; + protected final Icon arrowIcon; + protected final Font acceleratorFont; + protected final String acceleratorDelimiter; + + protected final int minimumWidth; + protected final Dimension minimumIconSize; + protected final int textAcceleratorGap; + protected final int textArrowGap; + + protected FlatMenuItemRenderer( JMenuItem menuItem, Icon checkIcon, Icon arrowIcon, + Font acceleratorFont, String acceleratorDelimiter ) + { + this.menuItem = menuItem; + this.checkIcon = checkIcon; + this.arrowIcon = arrowIcon; + this.acceleratorFont = acceleratorFont; + this.acceleratorDelimiter = acceleratorDelimiter; + + minimumWidth = UIManager.getInt( "MenuItem.minimumWidth" ); + Dimension minimumIconSize = UIManager.getDimension( "MenuItem.minimumIconSize" ); + this.minimumIconSize = (minimumIconSize != null) ? minimumIconSize : new Dimension( 16, 16 ); + this.textAcceleratorGap = FlatUIUtils.getUIInt( "MenuItem.textAcceleratorGap", 28 ); + this.textArrowGap = FlatUIUtils.getUIInt( "MenuItem.textArrowGap", 8 ); + } + + protected Dimension getPreferredMenuItemSize() { + int width = 0; + int height = 0; + boolean isTopLevelMenu = isTopLevelMenu( menuItem ); + + // icon size + if( !isTopLevelMenu ) { + Dimension iconSize = getIconSize(); + width += iconSize.width; + height = Math.max( iconSize.height, height ); + + // gap between icon and text + if( iconSize.width > 0 ) + width += scale( menuItem.getIconTextGap() ); + } + + // text size + String text = menuItem.getText(); + FontMetrics fm = menuItem.getFontMetrics( menuItem.getFont() ); + width += SwingUtilities.computeStringWidth( fm, text ); + height = Math.max( fm.getHeight(), height ); + + // accelerator size + String accelText = getAcceleratorText(); + if( accelText != null ) { + // gap between text and accelerator + width += scale( textAcceleratorGap ); + + FontMetrics accelFm = menuItem.getFontMetrics( acceleratorFont ); + width += SwingUtilities.computeStringWidth( accelFm, accelText ); + height = Math.max( accelFm.getHeight(), height ); + } + + // arrow size + if( !isTopLevelMenu && arrowIcon != null ) { + // gap between text and arrow + width += scale( textArrowGap ); + + width += arrowIcon.getIconWidth(); + height = Math.max( arrowIcon.getIconHeight(), height ); + } + + // add insets + Insets insets = menuItem.getInsets(); + width += insets.left + insets.right; + height += insets.top + insets.bottom; + + // minimum width + if( !isTopLevelMenu ) { + int minimumWidth = FlatUIUtils.minimumWidth( menuItem, this.minimumWidth ); + width = Math.max( width, scale( minimumWidth ) ); + } + + return new Dimension( width, height ); + } + + private void layout( Rectangle viewRect, Rectangle iconRect, Rectangle textRect, + Rectangle accelRect, Rectangle arrowRect ) + { + boolean isTopLevelMenu = isTopLevelMenu( menuItem ); + + // layout icon + iconRect.setSize( !isTopLevelMenu ? getIconSize() : new Dimension() ); + iconRect.y = viewRect.y + ((viewRect.height - iconRect.height) / 2); + + // layout text + FontMetrics fm = menuItem.getFontMetrics( menuItem.getFont() ); + textRect.width = SwingUtilities.computeStringWidth( fm, menuItem.getText() ); + textRect.height = fm.getHeight(); + textRect.y = viewRect.y + ((viewRect.height - textRect.height) / 2); + + // layout arrow + Icon arrowIcon = !isTopLevelMenu ? this.arrowIcon : null; + arrowRect.width = (arrowIcon != null) ? arrowIcon.getIconWidth() : 0; + arrowRect.height = (arrowIcon != null) ? arrowIcon.getIconHeight() : 0; + arrowRect.y = viewRect.y + ((viewRect.height - arrowRect.height) / 2); + + // layout accelerator + String accelText = getAcceleratorText(); + if( accelText != null ) { + FontMetrics accelFm = menuItem.getFontMetrics( acceleratorFont ); + accelRect.width = SwingUtilities.computeStringWidth( accelFm, accelText ); + accelRect.height = accelFm.getHeight(); + + accelRect.y = viewRect.y + ((viewRect.height - accelRect.height) / 2); + } else + accelRect.setBounds( 0, 0, 0, 0 ); + + if( menuItem.getComponentOrientation().isLeftToRight() ) { + // left-to-right + iconRect.x = viewRect.x; + textRect.x = iconRect.x + iconRect.width + + (!isTopLevelMenu && iconRect.width > 0 ? scale( menuItem.getIconTextGap() ) : 0); + arrowRect.x = viewRect.x + viewRect.width - arrowRect.width; + if( accelText != null ) + accelRect.x = arrowRect.x - accelRect.width; + } else { + // right-to-left + iconRect.x = viewRect.x + viewRect.width - iconRect.width; + textRect.x = iconRect.x - textRect.width + - (!isTopLevelMenu && iconRect.width > 0 ? scale( menuItem.getIconTextGap() ) : 0); + arrowRect.x = viewRect.x; + if( accelText != null ) + accelRect.x = arrowRect.x + arrowRect.width; + } + } + + protected void paintMenuItem( Graphics g, Color selectionBackground, Color selectionForeground, + Color disabledForeground, Color acceleratorForeground, Color acceleratorSelectionForeground ) + { + Rectangle viewRect = new Rectangle( menuItem.getWidth(), menuItem.getHeight() ); + + // subtract insets + Insets insets = menuItem.getInsets(); + viewRect.x += insets.left; + viewRect.y += insets.top; + viewRect.width -= (insets.left + insets.right); + viewRect.height -= (insets.top + insets.bottom); + + Rectangle iconRect = new Rectangle(); + Rectangle textRect = new Rectangle(); + Rectangle accelRect = new Rectangle(); + Rectangle arrowRect = new Rectangle(); + + layout( viewRect, iconRect, textRect, accelRect, arrowRect ); + +/*debug + g.setColor( Color.red ); g.drawRect( viewRect.x, viewRect.y, viewRect.width - 1, viewRect.height - 1 ); + g.setColor( Color.blue ); g.drawRect( iconRect.x, iconRect.y, iconRect.width - 1, iconRect.height - 1 ); + g.setColor( Color.cyan ); g.drawRect( textRect.x, textRect.y, textRect.width - 1, textRect.height - 1 ); + g.setColor( Color.magenta ); g.drawRect( accelRect.x, accelRect.y, accelRect.width - 1, accelRect.height - 1 ); + g.setColor( Color.orange ); g.drawRect( arrowRect.x, arrowRect.y, arrowRect.width - 1, arrowRect.height - 1 ); +debug*/ + + boolean isTopLevelMenu = isTopLevelMenu( menuItem ); + + paintBackground( g, selectionBackground ); + if( !isTopLevelMenu ) + paintIcon( g, iconRect, getIcon() ); + paintText( g, textRect, menuItem.getText(), selectionForeground, disabledForeground ); + paintAccelerator( g, accelRect, getAcceleratorText(), acceleratorForeground, acceleratorSelectionForeground, disabledForeground ); + if( !isTopLevelMenu ) + paintArrowIcon( g, arrowRect, arrowIcon ); + } + + protected void paintBackground( Graphics g, Color selectionBackground ) { + boolean armedOrSelected = isArmedOrSelected( menuItem ); + if( menuItem.isOpaque() || armedOrSelected ) { + g.setColor( armedOrSelected ? selectionBackground : menuItem.getBackground() ); + g.fillRect( 0, 0, menuItem.getWidth(), menuItem.getHeight() ); + } + } + + 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 ) { + int mnemonicIndex = FlatLaf.isShowMnemonics() ? menuItem.getDisplayedMnemonicIndex() : -1; + + paintText( g, menuItem, textRect, text, mnemonicIndex, menuItem.getFont(), + menuItem.getForeground(), selectionForeground, disabledForeground ); + } + + protected void paintAccelerator( Graphics g, Rectangle accelRect, String accelText, + Color foreground, Color selectionForeground, Color disabledForeground ) + { + paintText( g, menuItem, accelRect, accelText, -1, acceleratorFont, + foreground, selectionForeground, disabledForeground ); + } + + protected void paintArrowIcon( Graphics g, Rectangle arrowRect, Icon arrowIcon ) { + paintIcon( g, menuItem, arrowIcon, arrowRect ); + } + + protected static void paintIcon( Graphics g, JMenuItem menuItem, Icon icon, Rectangle iconRect ) { + if( icon == null ) + return; + + // center + int x = iconRect.x + ((iconRect.width - icon.getIconWidth()) / 2); + int y = iconRect.y + ((iconRect.height - icon.getIconHeight()) / 2); + + // paint + icon.paintIcon( menuItem, g, x, y ); + } + + protected static void paintText( Graphics g, JMenuItem menuItem, + Rectangle textRect, String text, int mnemonicIndex, Font font, + Color foreground, Color selectionForeground, Color disabledForeground ) + { + if( text == null || text.isEmpty() ) + return; + + FontMetrics fm = menuItem.getFontMetrics( font ); + + Font oldFont = g.getFont(); + g.setFont( font ); + g.setColor( !menuItem.isEnabled() + ? disabledForeground + : (isArmedOrSelected( menuItem ) + ? selectionForeground + : foreground) ); + + FlatUIUtils.drawStringUnderlineCharAt( menuItem, g, text, mnemonicIndex, + textRect.x, textRect.y + fm.getAscent() ); + + g.setFont( oldFont ); + } + + protected static boolean isArmedOrSelected( JMenuItem menuItem ) { + return menuItem.isArmed() || (menuItem instanceof JMenu && menuItem.isSelected()); + } + + protected static boolean isTopLevelMenu( JMenuItem menuItem ) { + return menuItem instanceof JMenu && ((JMenu)menuItem).isTopLevelMenu(); + } + + private Icon getIcon() { + return (checkIcon != null) ? checkIcon : menuItem.getIcon(); + } + + private Dimension getIconSize() { + Icon icon = getIcon(); + int iconWidth = (icon != null) ? icon.getIconWidth() : 0; + int iconHeight = (icon != null) ? icon.getIconHeight() : 0; + return new Dimension( + Math.max( iconWidth, scale( minimumIconSize.width ) ), + Math.max( iconHeight, scale( minimumIconSize.height ) ) ); + } + + private KeyStroke cachedAccelerator; + private String cachedAcceleratorText; + + private String getAcceleratorText() { + KeyStroke accelerator = menuItem.getAccelerator(); + if( accelerator == null ) + return null; + + if( accelerator == cachedAccelerator ) + return cachedAcceleratorText; + + StringBuilder buf = new StringBuilder(); + int modifiers = accelerator.getModifiers(); + if( modifiers != 0 ) + buf.append( InputEvent.getModifiersExText( modifiers ) ).append( acceleratorDelimiter ); + + int keyCode = accelerator.getKeyCode(); + if( keyCode != 0 ) + buf.append( KeyEvent.getKeyText( keyCode ) ); + else + buf.append( accelerator.getKeyChar() ); + + cachedAccelerator = accelerator; + cachedAcceleratorText = buf.toString(); + + return cachedAcceleratorText; + } +} 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 28c769996..b900776e0 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 @@ -16,19 +16,12 @@ package com.formdev.flatlaf.ui; -import static com.formdev.flatlaf.util.UIScale.scale; -import java.awt.Color; -import java.awt.FontMetrics; +import java.awt.Dimension; import java.awt.Graphics; -import java.awt.Rectangle; -import java.beans.PropertyChangeListener; -import javax.swing.ButtonModel; +import javax.swing.Icon; import javax.swing.JComponent; -import javax.swing.JMenu; -import javax.swing.JMenuItem; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicMenuItemUI; -import com.formdev.flatlaf.FlatLaf; /** * Provides the Flat LaF UI delegate for {@link javax.swing.JMenuItem}. @@ -53,11 +46,20 @@ * @uiDefault MenuItem.opaque boolean * @uiDefault MenuItem.evenHeight boolean * + * + * + * @uiDefault MenuItem.minimumIconSize Dimension + * @uiDefault MenuItem.textAcceleratorGap int + * @uiDefault MenuItem.acceleratorArrowGap int + * @uiDefault MenuItem.textArrowGap int + * * @author Karl Tauber */ public class FlatMenuItemUI extends BasicMenuItemUI { + private FlatMenuItemRenderer renderer; + public static ComponentUI createUI( JComponent c ) { return new FlatMenuItemUI(); } @@ -66,42 +68,28 @@ public static ComponentUI createUI( JComponent c ) { protected void installDefaults() { super.installDefaults(); - // scale - defaultTextIconGap = scale( defaultTextIconGap ); + renderer = createRenderer(); } - /** - * Scale defaultTextIconGap again if iconTextGap property has changed. - */ @Override - protected PropertyChangeListener createPropertyChangeListener( JComponent c ) { - PropertyChangeListener superListener = super.createPropertyChangeListener( c ); - return e -> { - superListener.propertyChange( e ); - if( e.getPropertyName() == "iconTextGap" ) - defaultTextIconGap = scale( defaultTextIconGap ); - }; - } + protected void uninstallDefaults() { + super.uninstallDefaults(); - @Override - protected void paintText( Graphics g, JMenuItem menuItem, Rectangle textRect, String text ) { - paintText( g, menuItem, textRect, text, disabledForeground, selectionForeground ); + renderer = null; } - public static void paintText( Graphics g, JMenuItem menuItem, Rectangle textRect, - String text, Color disabledForeground, Color selectionForeground ) - { - FontMetrics fm = menuItem.getFontMetrics( menuItem.getFont() ); - int mnemonicIndex = FlatLaf.isShowMnemonics() ? menuItem.getDisplayedMnemonicIndex() : -1; + protected FlatMenuItemRenderer createRenderer() { + return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter ); + } - ButtonModel model = menuItem.getModel(); - g.setColor( !model.isEnabled() - ? disabledForeground - : (model.isArmed() || (menuItem instanceof JMenu && model.isSelected()) - ? selectionForeground - : menuItem.getForeground()) ); + @Override + protected Dimension getPreferredMenuItemSize( JComponent c, Icon checkIcon, Icon arrowIcon, int defaultTextIconGap ) { + return renderer.getPreferredMenuItemSize(); + } - FlatUIUtils.drawStringUnderlineCharAt( menuItem, g, text, mnemonicIndex, - textRect.x, textRect.y + fm.getAscent() ); + @Override + public void paint( Graphics g, JComponent c ) { + renderer.paintMenuItem( g, selectionBackground, selectionForeground, disabledForeground, + acceleratorForeground, acceleratorSelectionForeground ); } } 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 aa2828753..909eaa025 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 @@ -16,13 +16,13 @@ package com.formdev.flatlaf.ui; -import static com.formdev.flatlaf.util.UIScale.scale; import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; import java.awt.Graphics; -import java.awt.Rectangle; import java.awt.event.MouseEvent; -import java.beans.PropertyChangeListener; import javax.swing.ButtonModel; +import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.JMenu; import javax.swing.JMenuItem; @@ -61,12 +61,20 @@ * * @uiDefault MenuBar.hoverBackground Color * + * + * + * @uiDefault MenuItem.minimumIconSize Dimension + * @uiDefault MenuItem.textAcceleratorGap int + * @uiDefault MenuItem.acceleratorArrowGap int + * @uiDefault MenuItem.textArrowGap int + * * @author Karl Tauber */ public class FlatMenuUI extends BasicMenuUI { private Color hoverBackground; + private FlatMenuItemRenderer renderer; public static ComponentUI createUI( JComponent c ) { return new FlatMenuUI(); @@ -79,9 +87,7 @@ protected void installDefaults() { menuItem.setRolloverEnabled( true ); hoverBackground = UIManager.getColor( "MenuBar.hoverBackground" ); - - // scale - defaultTextIconGap = scale( defaultTextIconGap ); + renderer = createRenderer(); } @Override @@ -89,19 +95,11 @@ protected void uninstallDefaults() { super.uninstallDefaults(); hoverBackground = null; + renderer = null; } - /** - * Scale defaultTextIconGap again if iconTextGap property has changed. - */ - @Override - protected PropertyChangeListener createPropertyChangeListener( JComponent c ) { - PropertyChangeListener superListener = super.createPropertyChangeListener( c ); - return e -> { - superListener.propertyChange( e ); - if( e.getPropertyName() == "iconTextGap" ) - defaultTextIconGap = scale( defaultTextIconGap ); - }; + protected FlatMenuItemRenderer createRenderer() { + return new FlatMenuRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter ); } @Override @@ -130,19 +128,37 @@ private void rollover( MouseEvent e, boolean rollover ) { } @Override - protected void paintBackground( Graphics g, JMenuItem menuItem, Color bgColor ) { - ButtonModel model = menuItem.getModel(); - if( model.isArmed() || model.isSelected() ) { - super.paintBackground( g, menuItem, bgColor ); - } else if( model.isRollover() && model.isEnabled() && ((JMenu)menuItem).isTopLevelMenu() ) { - FlatUIUtils.setColor( g, hoverBackground, menuItem.getBackground() ); - g.fillRect( 0, 0, menuItem.getWidth(), menuItem.getHeight() ); - } else - super.paintBackground( g, menuItem, bgColor ); + protected Dimension getPreferredMenuItemSize( JComponent c, Icon checkIcon, Icon arrowIcon, int defaultTextIconGap ) { + return renderer.getPreferredMenuItemSize(); } @Override - protected void paintText( Graphics g, JMenuItem menuItem, Rectangle textRect, String text ) { - FlatMenuItemUI.paintText( g, menuItem, textRect, text, disabledForeground, selectionForeground ); + public void paint( Graphics g, JComponent c ) { + renderer.paintMenuItem( g, selectionBackground, selectionForeground, disabledForeground, + acceleratorForeground, acceleratorSelectionForeground ); + } + + //---- class FlatMenuRenderer --------------------------------------------- + + protected class FlatMenuRenderer + extends FlatMenuItemRenderer + { + protected FlatMenuRenderer( JMenuItem menuItem, Icon checkIcon, Icon arrowIcon, + Font acceleratorFont, String acceleratorDelimiter ) + { + super( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter ); + } + + @Override + protected void paintBackground( Graphics g, Color selectionBackground ) { + ButtonModel model = menuItem.getModel(); + if( model.isRollover() && !model.isArmed() && !model.isSelected() && + model.isEnabled() && ((JMenu)menuItem).isTopLevelMenu() ) + { + FlatUIUtils.setColor( g, hoverBackground, menuItem.getBackground() ); + g.fillRect( 0, 0, menuItem.getWidth(), menuItem.getHeight() ); + } else + super.paintBackground( g, selectionBackground ); + } } } 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 ef55d3ec6..6160a786c 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 @@ -16,12 +16,10 @@ package com.formdev.flatlaf.ui; -import static com.formdev.flatlaf.util.UIScale.scale; +import java.awt.Dimension; import java.awt.Graphics; -import java.awt.Rectangle; -import java.beans.PropertyChangeListener; +import javax.swing.Icon; import javax.swing.JComponent; -import javax.swing.JMenuItem; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicRadioButtonMenuItemUI; @@ -48,11 +46,20 @@ * @uiDefault RadioButtonMenuItem.opaque boolean * @uiDefault RadioButtonMenuItem.evenHeight boolean * + * + * + * @uiDefault MenuItem.minimumIconSize Dimension + * @uiDefault MenuItem.textAcceleratorGap int + * @uiDefault MenuItem.acceleratorArrowGap int + * @uiDefault MenuItem.textArrowGap int + * * @author Karl Tauber */ public class FlatRadioButtonMenuItemUI extends BasicRadioButtonMenuItemUI { + private FlatMenuItemRenderer renderer; + public static ComponentUI createUI( JComponent c ) { return new FlatRadioButtonMenuItemUI(); } @@ -61,25 +68,28 @@ public static ComponentUI createUI( JComponent c ) { protected void installDefaults() { super.installDefaults(); - // scale - defaultTextIconGap = scale( defaultTextIconGap ); + renderer = createRenderer(); + } + + @Override + protected void uninstallDefaults() { + super.uninstallDefaults(); + + renderer = null; + } + + protected FlatMenuItemRenderer createRenderer() { + return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter ); } - /** - * Scale defaultTextIconGap again if iconTextGap property has changed. - */ @Override - protected PropertyChangeListener createPropertyChangeListener( JComponent c ) { - PropertyChangeListener superListener = super.createPropertyChangeListener( c ); - return e -> { - superListener.propertyChange( e ); - if( e.getPropertyName() == "iconTextGap" ) - defaultTextIconGap = scale( defaultTextIconGap ); - }; + protected Dimension getPreferredMenuItemSize( JComponent c, Icon checkIcon, Icon arrowIcon, int defaultTextIconGap ) { + return renderer.getPreferredMenuItemSize(); } @Override - protected void paintText( Graphics g, JMenuItem menuItem, Rectangle textRect, String text ) { - FlatMenuItemUI.paintText( g, menuItem, textRect, text, disabledForeground, selectionForeground ); + public void paint( Graphics g, JComponent c ) { + renderer.paintMenuItem( g, selectionBackground, selectionForeground, disabledForeground, + acceleratorForeground, acceleratorSelectionForeground ); } } 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 aa1521f47..8102e9645 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties @@ -299,7 +299,7 @@ Menu.background=@menuBackground MenuBar.border=com.formdev.flatlaf.ui.FlatMenuBarBorder MenuBar.background=@menuBackground -MenuBar.itemMargins=3,3,3,3 +MenuBar.itemMargins=3,8,3,8 #---- MenuItem ---- @@ -311,6 +311,10 @@ MenuItem.margin=@menuItemMargin MenuItem.opaque=false MenuItem.borderPainted=true MenuItem.background=@menuBackground +MenuItem.minimumWidth=72 +MenuItem.minimumIconSize=16,16 +MenuItem.textAcceleratorGap=24 +MenuItem.textArrowGap=8 MenuItem.acceleratorDelimiter=- [mac]MenuItem.acceleratorDelimiter=