diff --git a/CHANGELOG.md b/CHANGELOG.md index a26f9343..9b73cd09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ FlatLaf Change Log #### Fixed bugs +- Button: Fixed painting icon and text at wrong location when using HTML text, + left/right vertical alignment and running in Java 19+. (issue #746) - CheckBox and RadioButton: Fixed cut off right side when border is removed and horizontal alignment is set to `right`. (issue #734) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonUI.java index 7fd679b3..6ff9c789 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonUI.java @@ -53,6 +53,8 @@ import javax.swing.plaf.ToolBarUI; import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicButtonListener; import javax.swing.plaf.basic.BasicButtonUI; +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.FlatHelpButtonIcon; @@ -551,9 +553,45 @@ public class FlatButtonUI } } + /** + * Similar to BasicButtonUI.paint(), but does not use zero insets for HTML text, + * which is done in BasicButtonUI.layout() since Java 19. + * See https://github.com/openjdk/jdk/pull/8407 + * and https://github.com/openjdk/jdk/pull/8407#issuecomment-1761583430 + */ @Override public void paint( Graphics g, JComponent c ) { - super.paint( FlatLabelUI.createGraphicsHTMLTextYCorrection( g, c ), c ); + g = FlatLabelUI.createGraphicsHTMLTextYCorrection( g, c ); + + AbstractButton b = (AbstractButton) c; + + // layout + String clippedText = layout( b, b.getFontMetrics( b.getFont() ), b.getWidth(), b.getHeight() ); + + // not used in FlatLaf, but invoked for compatibility with BasicButtonUI.paint() + clearTextShiftOffset(); + + // not used in FlatLaf, but invoked for compatibility with BasicButtonUI.paint() + ButtonModel model = b.getModel(); + if( model.isArmed() && model.isPressed() ) + paintButtonPressed( g, b ); + + // paint icon + if( b.getIcon() != null ) + paintIcon( g, b, iconR ); + + // paint text + if( clippedText != null && !clippedText.isEmpty() ) { + View view = (View) b.getClientProperty( BasicHTML.propertyKey ); + if( view != null ) + view.paint( g, textR ); // HTML text + else + paintText( g, b, textR, clippedText ); + } + + // not used in FlatLaf, but invoked for compatibility with BasicButtonUI.paint() + if( b.isFocusPainted() && b.hasFocus() ) + paintFocus( g, b, viewR, textR, iconR ); } @Override @@ -786,6 +824,67 @@ public class FlatButtonUI return margin instanceof UIResource && Objects.equals( margin, defaultMargin ); } + @Override + public int getBaseline( JComponent c, int width, int height ) { + return getBaselineImpl( c, width, height ); + } + + /** + * Similar to BasicButtonUI.getBaseline(), but does not use zero insets for HTML text, + * which is done in BasicButtonUI.layout() since Java 19. + * See https://github.com/openjdk/jdk/pull/8407 + * and https://github.com/openjdk/jdk/pull/8407#issuecomment-1761583430 + */ + static int getBaselineImpl( JComponent c, int width, int height ) { + if( width < 0 || height < 0 ) + throw new IllegalArgumentException(); + + AbstractButton b = (AbstractButton) c; + String text = b.getText(); + if( text == null || text.isEmpty() ) + return -1; + + FontMetrics fm = b.getFontMetrics( b.getFont() ); + layout( b, fm, width, height ); + + View view = (View) b.getClientProperty( BasicHTML.propertyKey ); + if( view != null ) { + // HTML text + int baseline = BasicHTML.getHTMLBaseline( view, textR.width, textR.height ); + return (baseline >= 0) ? textR.y + baseline : baseline; + } else + return textR.y + fm.getAscent(); + } + + /** + * Similar to BasicButtonUI.layout(), but does not use zero insets for HTML text, + * which is done in BasicButtonUI.layout() since Java 19. + * See https://github.com/openjdk/jdk/pull/8407 + * and https://github.com/openjdk/jdk/pull/8407#issuecomment-1761583430 + */ + private static String layout( AbstractButton b, FontMetrics fm, int width, int height ) { + // compute view rectangle + Insets insets = b.getInsets(); + viewR.setBounds( insets.left, insets.top, + width - insets.left - insets.right, + height - insets.top - insets.bottom ); + + // reset rectangles + textR.setBounds( 0, 0, 0, 0 ); + iconR.setBounds( 0, 0, 0, 0 ); + + String text = b.getText(); + return SwingUtilities.layoutCompoundLabel( b, fm, text, b.getIcon(), + b.getVerticalAlignment(), b.getHorizontalAlignment(), + b.getVerticalTextPosition(), b.getHorizontalTextPosition(), + viewR, iconR, textR, + (text != null) ? b.getIconTextGap() : 0 ); + } + + private static Rectangle viewR = new Rectangle(); + private static Rectangle textR = new Rectangle(); + private static Rectangle iconR = new Rectangle(); + //---- class FlatButtonListener ------------------------------------------- protected class FlatButtonListener diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonUI.java index 24b1998c..c58e3798 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRadioButtonUI.java @@ -336,6 +336,11 @@ public class FlatRadioButtonUI : 0; } + @Override + public int getBaseline( JComponent c, int width, int height ) { + return FlatButtonUI.getBaselineImpl( c, width, height ); + } + //---- class FlatRadioButtonListener -------------------------------------- /** @since 2 */ diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/jdk/HtmlButtonTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/jdk/HtmlButtonTest.java new file mode 100644 index 00000000..b2fac0ad --- /dev/null +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/jdk/HtmlButtonTest.java @@ -0,0 +1,55 @@ +package com.formdev.flatlaf.testing.jdk; + +import java.awt.*; +import javax.swing.*; +import javax.swing.border.*; +import com.formdev.flatlaf.FlatLightLaf; + +/** + * https://github.com/openjdk/jdk/pull/8407#issuecomment-1761583430 + */ +public class HtmlButtonTest +{ + public static void main( String[] args ) { + SwingUtilities.invokeLater( () -> { + FlatLightLaf.setup(); + + JFrame frame = new JFrame( "HTML Button Test" ); + frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); + + JPanel panel = new JPanel( new GridBagLayout() ); + panel.setBorder( new EmptyBorder( 20, 20, 20, 20 ) ); + + createButtons( panel, "center", SwingConstants.CENTER, SwingConstants.CENTER, null ); + createButtons( panel, "left", SwingConstants.LEFT, SwingConstants.CENTER, null ); + createButtons( panel, "right", SwingConstants.RIGHT, SwingConstants.CENTER, null ); + + createButtons( panel, "center with margin 30,4,4,4", SwingConstants.CENTER, SwingConstants.CENTER, new Insets( 30, 4, 4, 4 ) ); + createButtons( panel, "left with margin 30,4,4,4", SwingConstants.LEFT, SwingConstants.CENTER, new Insets( 30, 4, 4, 4 ) ); + createButtons( panel, "left/top with margin 30,4,4,4", SwingConstants.LEFT, SwingConstants.TOP, new Insets( 30, 4, 4, 4 ) ); + + frame.add( new JLabel( "Java version " + System.getProperty( "java.version" ) ), BorderLayout.NORTH ); + frame.add( panel ); + frame.pack(); + frame.setVisible( true ); + } ); + } + + private static void createButtons( JPanel panel, String text, int horizontalAlignment, int verticalAlignment, Insets margin ) { + JButton button = new JButton( text ); + button.setHorizontalAlignment( horizontalAlignment ); + button.setVerticalAlignment( verticalAlignment ); + if( margin != null ) + button.setMargin( margin ); + panel.add( button, new GridBagConstraints( 0, GridBagConstraints.RELATIVE, 1, 1, 1.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets( 4, 4, 4, 4 ), 0, 0 ) ); + + JButton htmlButton = new JButton( "HTML " + text + "" ); + htmlButton.setHorizontalAlignment( horizontalAlignment ); + htmlButton.setVerticalAlignment( verticalAlignment ); + if( margin != null ) + htmlButton.setMargin( margin ); + panel.add( htmlButton, new GridBagConstraints( 0, GridBagConstraints.RELATIVE, 1, 1, 1.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets( 4, 4, 24, 4 ), 0, 0 ) ); + } +} \ No newline at end of file