diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePane.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePane.java index 014b5d53..568d9594 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePane.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePane.java @@ -19,8 +19,10 @@ package com.formdev.flatlaf.ui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dialog; +import java.awt.Dimension; import java.awt.Frame; import java.awt.Graphics; +import java.awt.Image; import java.awt.Window; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; @@ -29,6 +31,7 @@ import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.util.List; import javax.accessibility.AccessibleContext; import javax.swing.BorderFactory; import javax.swing.BoxLayout; @@ -39,7 +42,6 @@ import javax.swing.JPanel; import javax.swing.JRootPane; import javax.swing.SwingUtilities; import javax.swing.UIManager; -import com.formdev.flatlaf.util.UIScale; /** * Provides the Flat LaF title bar. @@ -48,6 +50,9 @@ import com.formdev.flatlaf.util.UIScale; * @uiDefault TitlePane.inactiveBackground Color * @uiDefault TitlePane.foreground Color * @uiDefault TitlePane.inactiveForeground Color + * @uiDefault TitlePane.iconSize Dimension + * @uiDefault TitlePane.iconMargins Insets + * @uiDefault TitlePane.titleMargins Insets * @uiDefault TitlePane.closeIcon Icon * @uiDefault TitlePane.iconifyIcon Icon * @uiDefault TitlePane.maximizeIcon Icon @@ -63,8 +68,11 @@ class FlatTitlePane private final Color activeForeground = UIManager.getColor( "TitlePane.foreground" ); private final Color inactiveForeground = UIManager.getColor( "TitlePane.inactiveForeground" ); + private final Dimension iconSize = UIManager.getDimension( "TitlePane.iconSize" ); + private final JRootPane rootPane; + private JLabel iconLabel; private JLabel titleLabel; private JPanel buttonPanel; private JButton iconifyButton; @@ -85,12 +93,15 @@ class FlatTitlePane } private void addSubComponents() { + iconLabel = new JLabel(); titleLabel = new JLabel(); + iconLabel.setBorder( new FlatEmptyBorder( UIManager.getInsets( "TitlePane.iconMargins" ) ) ); titleLabel.setBorder( new FlatEmptyBorder( UIManager.getInsets( "TitlePane.titleMargins" ) ) ); createButtons(); - setLayout( new BorderLayout( UIScale.scale( 4 ), 0 ) ); + setLayout( new BorderLayout() ); + add( iconLabel, BorderLayout.WEST ); add( titleLabel, BorderLayout.CENTER ); add( buttonPanel, BorderLayout.EAST ); } @@ -157,6 +168,26 @@ class FlatTitlePane } } + private void updateIcon() { + // get window images + List images = window.getIconImages(); + if( images.isEmpty() ) { + // search in owners + for( Window owner = window.getOwner(); owner != null; owner = owner.getOwner() ) { + images = owner.getIconImages(); + if( !images.isEmpty() ) + break; + } + } + + // show/hide icon + boolean hasImages = !images.isEmpty(); + iconLabel.setVisible( hasImages ); + + if( hasImages ) + iconLabel.setIcon( FlatTitlePaneIcon.create( images, iconSize ) ); + } + @Override public void addNotify() { super.addNotify(); @@ -167,6 +198,7 @@ class FlatTitlePane if( window != null ) { frameStateChanged(); activeChanged( window.isActive() ); + updateIcon(); titleLabel.setText( getWindowTitle() ); installWindowListeners(); } @@ -260,6 +292,10 @@ class FlatTitlePane if( window instanceof Frame ) frameStateChanged(); break; + + case "iconImage": + updateIcon(); + break; } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePaneIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePaneIcon.java new file mode 100644 index 00000000..93084ccc --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePaneIcon.java @@ -0,0 +1,70 @@ +/* + * 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 java.awt.Dimension; +import java.awt.Image; +import java.util.ArrayList; +import java.util.List; +import javax.swing.Icon; +import javax.swing.ImageIcon; +import com.formdev.flatlaf.util.MultiResolutionImageSupport; +import com.formdev.flatlaf.util.ScaledImageIcon; + +/** + * @author Karl Tauber + */ +class FlatTitlePaneIcon + extends ScaledImageIcon +{ + static Icon create( List images, Dimension size ) { + // collect all images including multi-resolution variants + List allImages = new ArrayList<>(); + for( Image image : images ) { + if( MultiResolutionImageSupport.isMultiResolutionImage( image ) ) + allImages.addAll( MultiResolutionImageSupport.getResolutionVariants( image ) ); + else + allImages.add( image ); + } + + // sort images by size + allImages.sort( (image1, image2) -> { + return image1.getWidth( null ) - image2.getWidth( null ); + } ); + + // create icon + return new FlatTitlePaneIcon( allImages, size ); + } + + private final List images; + + FlatTitlePaneIcon( List images, Dimension size ) { + super( new ImageIcon( images.get( 0 ) ), size.width, size.height ); + this.images = images; + } + + @Override + protected Image getResolutionVariant( int destImageWidth, int destImageHeight ) { + for( Image image : images ) { + if( destImageWidth <= image.getWidth( null ) && + destImageHeight <= image.getHeight( null ) ) + return image; + } + + return images.get( images.size() - 1 ); + } +} diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/ScaledImageIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/ScaledImageIcon.java index 91c5549f..cb503c31 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/ScaledImageIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/ScaledImageIcon.java @@ -37,30 +37,38 @@ public class ScaledImageIcon implements Icon { private final ImageIcon imageIcon; + private final int iconWidth; + private final int iconHeight; private double lastSystemScaleFactor; private float lastUserScaleFactor; private Image lastImage; public ScaledImageIcon( ImageIcon imageIcon ) { + this( imageIcon, imageIcon.getIconWidth(), imageIcon.getIconHeight() ); + } + + public ScaledImageIcon( ImageIcon imageIcon, int iconWidth, int iconHeight ) { this.imageIcon = imageIcon; + this.iconWidth = iconWidth; + this.iconHeight = iconHeight; } @Override public int getIconWidth() { - return UIScale.scale( imageIcon.getIconWidth() ); + return UIScale.scale( iconWidth ); } @Override public int getIconHeight() { - return UIScale.scale( imageIcon.getIconHeight() ); + return UIScale.scale( iconHeight ); } @Override public void paintIcon( Component c, Graphics g, int x, int y ) { /*debug g.setColor( Color.red ); - g.drawRect( x, y, getIconWidth(), getIconHeight() ); + g.drawRect( x, y, getIconWidth() - 1, getIconHeight() - 1 ); debug*/ // scale factor @@ -69,7 +77,7 @@ debug*/ double scaleFactor = systemScaleFactor * userScaleFactor; // paint input image icon if not necessary to scale - if( scaleFactor == 1 ) { + if( scaleFactor == 1 && iconWidth == imageIcon.getIconWidth() && iconHeight == imageIcon.getIconHeight() ) { imageIcon.paintIcon( c, g, x, y ); return; } @@ -84,12 +92,11 @@ debug*/ } // destination image size - int destImageWidth = (int) Math.round( imageIcon.getIconWidth() * scaleFactor ); - int destImageHeight = (int) Math.round( imageIcon.getIconHeight() * scaleFactor ); + int destImageWidth = (int) Math.round( iconWidth * scaleFactor ); + int destImageHeight = (int) Math.round( iconHeight * scaleFactor ); // get resolution variant of image if it is a multi-resolution image - Image image = MultiResolutionImageSupport.getResolutionVariant( - imageIcon.getImage(), destImageWidth, destImageHeight ); + Image image = getResolutionVariant( destImageWidth, destImageHeight ); // size of image int imageWidth = image.getWidth( null ); @@ -124,6 +131,11 @@ debug*/ paintLastImage( g, x, y ); } + protected Image getResolutionVariant( int destImageWidth, int destImageHeight ) { + return MultiResolutionImageSupport.getResolutionVariant( + imageIcon.getImage(), destImageWidth, destImageHeight ); + } + private void paintLastImage( Graphics g, int x, int y ) { if( lastSystemScaleFactor > 1 ) { HiDPIUtils.paintAtScale1x( (Graphics2D) g, x, y, 100, 100, // width and height are not used 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 98094770..5b6f4d2a 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties @@ -576,6 +576,8 @@ TitledBorder.border=1,1,1,1,$Separator.foreground #---- TitlePane ---- +TitlePane.iconSize=16,16 +TitlePane.iconMargins=3,8,3,0 TitlePane.titleMargins=3,8,3,8 TitlePane.closeIcon=$InternalFrame.closeIcon TitlePane.iconifyIcon=$InternalFrame.iconifyIcon diff --git a/flatlaf-testing/Window Icon Test Images.sketch b/flatlaf-testing/Window Icon Test Images.sketch new file mode 100644 index 00000000..3fa6d1df Binary files /dev/null and b/flatlaf-testing/Window Icon Test Images.sketch differ diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.java index 6d4efdab..58f35611 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.java @@ -17,7 +17,11 @@ package com.formdev.flatlaf.testing; import java.awt.*; +import java.awt.Dialog.ModalityType; import java.awt.event.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import javax.swing.*; import net.miginfocom.swing.*; @@ -39,10 +43,25 @@ public class FlatWindowDecorationsTest // frame.setUndecorated( true ); // frame.getRootPane().setWindowDecorationStyle( JRootPane.FRAME ); + Class cls = FlatWindowDecorationsTest.class; + List images = Arrays.asList( + new ImageIcon( cls.getResource( "/com/formdev/flatlaf/testing/test16.png" ) ).getImage(), + new ImageIcon( cls.getResource( "/com/formdev/flatlaf/testing/test24.png" ) ).getImage(), + new ImageIcon( cls.getResource( "/com/formdev/flatlaf/testing/test32.png" ) ).getImage(), + new ImageIcon( cls.getResource( "/com/formdev/flatlaf/testing/test48.png" ) ).getImage(), + new ImageIcon( cls.getResource( "/com/formdev/flatlaf/testing/test64.png" ) ).getImage(), + new ImageIcon( cls.getResource( "/com/formdev/flatlaf/testing/test128.png" ) ).getImage() + ); + // shuffle to test whether FlatLaf chooses the right size + Collections.shuffle( images ); + frame.setIconImages( images ); + frame.showFrame( FlatWindowDecorationsTest::new, panel -> ((FlatWindowDecorationsTest)panel).menuBar ); } ); } + private List images; + FlatWindowDecorationsTest() { initComponents(); } @@ -51,6 +70,14 @@ public class FlatWindowDecorationsTest public void addNotify() { super.addNotify(); + Window window = SwingUtilities.windowForComponent( this ); + menuBarCheckBox.setEnabled( window instanceof JFrame ); + + boolean windowHasIcons = (window != null && !window.getIconImages().isEmpty()); + iconNoneRadioButton.setEnabled( windowHasIcons ); + iconTestAllRadioButton.setEnabled( windowHasIcons ); + iconTestRandomRadioButton.setEnabled( windowHasIcons ); + JRootPane rootPane = getWindowRootPane(); if( rootPane != null ) { int style = rootPane.getWindowDecorationStyle(); @@ -89,7 +116,12 @@ public class FlatWindowDecorationsTest } private void openDialog() { - JOptionPane.showMessageDialog( this, new FlatWindowDecorationsTest() ); + Window owner = SwingUtilities.windowForComponent( this ); + JDialog dialog = new JDialog( owner, "Dialog", ModalityType.APPLICATION_MODAL ); + dialog.add( new FlatWindowDecorationsTest() ); + dialog.pack(); + dialog.setLocationRelativeTo( this ); + dialog.setVisible( true ); } private void decorationStyleChanged() { @@ -118,6 +150,22 @@ public class FlatWindowDecorationsTest rootPane.setWindowDecorationStyle( style ); } + private void iconChanged() { + Window window = SwingUtilities.windowForComponent( this ); + if( window == null ) + return; + + if( images == null ) + images = window.getIconImages(); + + if( iconNoneRadioButton.isSelected() ) + window.setIconImage( null ); + else if( iconTestAllRadioButton.isSelected() ) + window.setIconImages( images ); + else if( iconTestRandomRadioButton.isSelected() ) + window.setIconImage( images.get( (int) (Math.random() * images.size()) ) ); + } + private JRootPane getWindowRootPane() { Window window = SwingUtilities.windowForComponent( this ); if( window instanceof JFrame ) @@ -132,6 +180,7 @@ public class FlatWindowDecorationsTest menuBarCheckBox = new JCheckBox(); resizableCheckBox = new JCheckBox(); JLabel label1 = new JLabel(); + JLabel label2 = new JLabel(); JPanel panel1 = new JPanel(); styleNoneRadioButton = new JRadioButton(); styleFrameRadioButton = new JRadioButton(); @@ -142,6 +191,10 @@ public class FlatWindowDecorationsTest styleWarningRadioButton = new JRadioButton(); styleColorChooserRadioButton = new JRadioButton(); styleFileChooserRadioButton = new JRadioButton(); + JPanel panel2 = new JPanel(); + iconNoneRadioButton = new JRadioButton(); + iconTestAllRadioButton = new JRadioButton(); + iconTestRandomRadioButton = new JRadioButton(); JButton openDialogButton = new JButton(); menuBar = new JMenuBar(); JMenu fileMenu = new JMenu(); @@ -174,12 +227,13 @@ public class FlatWindowDecorationsTest setLayout(new MigLayout( "ltr,insets dialog,hidemode 3", // columns - "[left]para", + "[left]para" + + "[fill]", // rows - "para[]" + - "[]" + + "para[]0" + "[]" + "[]" + + "[top]" + "[]")); //---- menuBarCheckBox ---- @@ -198,6 +252,10 @@ public class FlatWindowDecorationsTest label1.setText("Style:"); add(label1, "cell 0 2"); + //---- label2 ---- + label2.setText("Icon:"); + add(label2, "cell 1 2"); + //======== panel1 ======== { panel1.setLayout(new MigLayout( @@ -263,6 +321,35 @@ public class FlatWindowDecorationsTest } add(panel1, "cell 0 3"); + //======== panel2 ======== + { + panel2.setLayout(new MigLayout( + "ltr,insets 0,hidemode 3,gap 0 0", + // columns + "[fill]", + // rows + "[]" + + "[]" + + "[]")); + + //---- iconNoneRadioButton ---- + iconNoneRadioButton.setText("none"); + iconNoneRadioButton.addActionListener(e -> iconChanged()); + panel2.add(iconNoneRadioButton, "cell 0 0"); + + //---- iconTestAllRadioButton ---- + iconTestAllRadioButton.setText("test all"); + iconTestAllRadioButton.setSelected(true); + iconTestAllRadioButton.addActionListener(e -> iconChanged()); + panel2.add(iconTestAllRadioButton, "cell 0 1"); + + //---- iconTestRandomRadioButton ---- + iconTestRandomRadioButton.setText("test random"); + iconTestRandomRadioButton.addActionListener(e -> iconChanged()); + panel2.add(iconTestRandomRadioButton, "cell 0 2"); + } + add(panel2, "cell 1 3"); + //---- openDialogButton ---- openDialogButton.setText("Open Dialog"); openDialogButton.addActionListener(e -> openDialog()); @@ -447,6 +534,12 @@ public class FlatWindowDecorationsTest styleButtonGroup.add(styleWarningRadioButton); styleButtonGroup.add(styleColorChooserRadioButton); styleButtonGroup.add(styleFileChooserRadioButton); + + //---- iconButtonGroup ---- + ButtonGroup iconButtonGroup = new ButtonGroup(); + iconButtonGroup.add(iconNoneRadioButton); + iconButtonGroup.add(iconTestAllRadioButton); + iconButtonGroup.add(iconTestRandomRadioButton); // JFormDesigner - End of component initialization //GEN-END:initComponents } @@ -462,6 +555,9 @@ public class FlatWindowDecorationsTest private JRadioButton styleWarningRadioButton; private JRadioButton styleColorChooserRadioButton; private JRadioButton styleFileChooserRadioButton; + private JRadioButton iconNoneRadioButton; + private JRadioButton iconTestAllRadioButton; + private JRadioButton iconTestRandomRadioButton; private JMenuBar menuBar; // JFormDesigner - End of variables declaration //GEN-END:variables } diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.jfd index dfaee803..a9824228 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.jfd @@ -8,8 +8,8 @@ new FormModel { } add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "ltr,insets dialog,hidemode 3" - "$columnConstraints": "[left]para" - "$rowConstraints": "para[][][][][]" + "$columnConstraints": "[left]para[fill]" + "$rowConstraints": "para[]0[][][top][]" } ) { name: "this" add( new FormComponent( "javax.swing.JCheckBox" ) { @@ -40,6 +40,12 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 2" } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "label2" + "text": "Icon:" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 2" + } ) add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$columnConstraints": "[fill]" "$rowConstraints": "[][][][][][][][][]" @@ -149,6 +155,49 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 3" } ) + add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$columnConstraints": "[fill]" + "$rowConstraints": "[][][]" + "$layoutConstraints": "ltr,insets 0,hidemode 3,gap 0 0" + } ) { + name: "panel2" + add( new FormComponent( "javax.swing.JRadioButton" ) { + name: "iconNoneRadioButton" + "text": "none" + "$buttonGroup": new FormReference( "iconButtonGroup" ) + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "iconChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0" + } ) + add( new FormComponent( "javax.swing.JRadioButton" ) { + name: "iconTestAllRadioButton" + "text": "test all" + "selected": true + "$buttonGroup": new FormReference( "iconButtonGroup" ) + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "iconChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1" + } ) + add( new FormComponent( "javax.swing.JRadioButton" ) { + name: "iconTestRandomRadioButton" + "text": "test random" + "$buttonGroup": new FormReference( "iconButtonGroup" ) + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "iconChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 2" + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 3" + } ) add( new FormComponent( "javax.swing.JButton" ) { name: "openDialogButton" "text": "Open Dialog" @@ -332,5 +381,10 @@ new FormModel { }, new FormLayoutConstraints( null ) { "location": new java.awt.Point( 0, 450 ) } ) + add( new FormNonVisual( "javax.swing.ButtonGroup" ) { + name: "iconButtonGroup" + }, new FormLayoutConstraints( null ) { + "location": new java.awt.Point( 0, 502 ) + } ) } } diff --git a/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test128.png b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test128.png new file mode 100644 index 00000000..b26ffcea Binary files /dev/null and b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test128.png differ diff --git a/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test16.png b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test16.png new file mode 100644 index 00000000..2e0e2727 Binary files /dev/null and b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test16.png differ diff --git a/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test24.png b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test24.png new file mode 100644 index 00000000..bb6da8bf Binary files /dev/null and b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test24.png differ diff --git a/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test32.png b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test32.png new file mode 100644 index 00000000..1cf56f82 Binary files /dev/null and b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test32.png differ diff --git a/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test48.png b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test48.png new file mode 100644 index 00000000..9641e283 Binary files /dev/null and b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test48.png differ diff --git a/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test64.png b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test64.png new file mode 100644 index 00000000..ad67439b Binary files /dev/null and b/flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/test64.png differ