From 29e1dc6b55dcc897b0770dcc1f33957321da715c Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Fri, 14 May 2021 16:43:47 +0200 Subject: [PATCH] FlatTitlePaneIcon: use `getResolutionVariant(width, height)` instead of `getResolutionVariants()` to allow creation of requested size on demand and to avoids creation of all resolution variants Extras: `FlatSVGUtils.createWindowIconImages()` now returns a single multi-resolution image that creates requested image sizes on demand from SVG (issue #323) --- CHANGELOG.md | 7 +++ .../com/formdev/flatlaf/ui/FlatTitlePane.java | 2 +- .../formdev/flatlaf/ui/FlatTitlePaneIcon.java | 41 ++++++++-------- .../formdev/flatlaf/util/ScaledImageIcon.java | 2 +- .../formdev/flatlaf/extras/FlatSVGUtils.java | 48 +++++++++++++++---- .../testing/FlatWindowDecorationsTest.java | 48 +++++++++++++++++++ .../testing/FlatWindowDecorationsTest.jfd | 32 +++++++++++-- 7 files changed, 145 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8190efd0..7a93f502 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,9 @@ FlatLaf Change Log - Getters for icon name, classloader, etc. - Extras: UI Inspector: Show class hierarchies when pressing Alt key and prettified class names (dimmed package name). +- Extras: `FlatSVGUtils.createWindowIconImages()` now returns a single + multi-resolution image that creates requested image sizes on demand from SVG + (Java 9+). #### Fixed bugs @@ -58,6 +61,10 @@ FlatLaf Change Log dialogs. (issue #315) - Fixed broken maximizing window (under special conditions) when restoring frame state at startup. + - Title icon: For multi-resolution images now use `getResolutionVariant(width, + height)` (instead of `getResolutionVariants()`) to allow creation of + requested size on demand. This also avoids creation of all resolution + variants. - Linux: Fixed/improved detection of user font settings. (issue #309) 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 ef5baa1c..9ff1ba3d 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 @@ -351,7 +351,7 @@ public class FlatTitlePane // set icon if( !images.isEmpty() ) - iconLabel.setIcon( FlatTitlePaneIcon.create( images, iconSize ) ); + iconLabel.setIcon( new FlatTitlePaneIcon( images, iconSize ) ); else { // no icon set on window --> use default icon Icon defaultIcon = UIManager.getIcon( "TitlePane.icon" ); 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 index fbe166a0..de84a12e 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePaneIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePaneIcon.java @@ -20,8 +20,6 @@ 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; @@ -31,40 +29,43 @@ import com.formdev.flatlaf.util.ScaledImageIcon; public class FlatTitlePaneIcon extends ScaledImageIcon { - public static Icon create( List images, Dimension size ) { - // collect all images including multi-resolution variants + private final List images; + + /** + * @since 1.2 + */ + public FlatTitlePaneIcon( List images, Dimension size ) { + super( null, size.width, size.height ); + this.images = images; + } + + @Override + protected Image getResolutionVariant( int destImageWidth, int destImageHeight ) { + // collect all images including multi-resolution variants for requested size List allImages = new ArrayList<>(); for( Image image : images ) { if( MultiResolutionImageSupport.isMultiResolutionImage( image ) ) - allImages.addAll( MultiResolutionImageSupport.getResolutionVariants( image ) ); + allImages.add( MultiResolutionImageSupport.getResolutionVariant( image, destImageWidth, destImageHeight ) ); else allImages.add( image ); } + if( allImages.size() == 1 ) + return allImages.get( 0 ); + // 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; - - private 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 ) { + // search for optimal image size + for( Image image : allImages ) { if( destImageWidth <= image.getWidth( null ) && destImageHeight <= image.getHeight( null ) ) return image; } - return images.get( images.size() - 1 ); + // use largest image + return allImages.get( allImages.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 cb503c31..0d683ac9 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 @@ -77,7 +77,7 @@ debug*/ double scaleFactor = systemScaleFactor * userScaleFactor; // paint input image icon if not necessary to scale - if( scaleFactor == 1 && iconWidth == imageIcon.getIconWidth() && iconHeight == imageIcon.getIconHeight() ) { + if( scaleFactor == 1 && imageIcon != null && iconWidth == imageIcon.getIconWidth() && iconHeight == imageIcon.getIconHeight() ) { imageIcon.paintIcon( c, g, x, y ); return; } diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatSVGUtils.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatSVGUtils.java index f40d3d83..2ba944e6 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatSVGUtils.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatSVGUtils.java @@ -16,6 +16,7 @@ package com.formdev.flatlaf.extras; +import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Image; import java.awt.RenderingHints; @@ -23,8 +24,10 @@ import java.awt.image.BufferedImage; import java.net.URISyntaxException; import java.net.URL; import java.util.Arrays; +import java.util.Collections; import java.util.List; import javax.swing.JWindow; +import com.formdev.flatlaf.util.MultiResolutionImageSupport; import com.kitfox.svg.SVGCache; import com.kitfox.svg.SVGDiagram; import com.kitfox.svg.SVGException; @@ -40,22 +43,51 @@ public class FlatSVGUtils * Creates from the given SVG a list of icon images with different sizes that * can be used for windows headers. The SVG should have a size of 16x16, * otherwise it is scaled. + *

+ * If running on Java 9 or later and multi-resolution image support is available, + * then a single multi-resolution image is returned that creates images on demand + * for requested sizes from SVG. + * This has the advantage that only images for used sizes are created. + * Also if unusual sizes are requested (e.g. 18x18), then they are created from SVG. * * @param svgName the name of the SVG resource (a '/'-separated path) - * @return list of icon images with different sizes (16x16, 24x24, 32x32, 48x48 and 64x64) + * @return list of icon images with different sizes (16x16, 20x20, 24x24, 28x28, 32x32, 48x48 and 64x64) * @throws RuntimeException if failed to load or render SVG file * @see JWindow#setIconImages(List) */ public static List createWindowIconImages( String svgName ) { SVGDiagram diagram = loadSVG( svgName ); - return Arrays.asList( - svg2image( diagram, 16, 16 ), - svg2image( diagram, 24, 24 ), - svg2image( diagram, 32, 32 ), - svg2image( diagram, 48, 48 ), - svg2image( diagram, 64, 64 ) - ); + if( MultiResolutionImageSupport.isAvailable() ) { + // use a multi-resolution image that creates images on demand for requested sizes + return Collections.singletonList( MultiResolutionImageSupport.create( 0, + new Dimension[] { + // Listing all these sizes here is actually not necessary because + // any size is created on demand when + // MultiResolutionImage.getResolutionVariant(double destImageWidth, double destImageHeight) + // is invoked. + // This sizes are only used by MultiResolutionImage.getResolutionVariants(). + new Dimension( 16, 16 ), // 100% + new Dimension( 20, 20 ), // 125% + new Dimension( 24, 24 ), // 150% + new Dimension( 28, 28 ), // 175% + new Dimension( 32, 32 ), // 200% + new Dimension( 48, 48 ), // 300% + new Dimension( 64, 64 ), // 400% + }, dim -> { + return svg2image( diagram, dim.width, dim.height ); + } ) ); + } else { + return Arrays.asList( + svg2image( diagram, 16, 16 ), // 100% + svg2image( diagram, 20, 20 ), // 125% + svg2image( diagram, 24, 24 ), // 150% + svg2image( diagram, 28, 28 ), // 175% + svg2image( diagram, 32, 32 ), // 200% + svg2image( diagram, 48, 48 ), // 300% + svg2image( diagram, 64, 64 ) // 400% + ); + } } /** 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 54f6704c..41e7f04a 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 @@ -19,12 +19,16 @@ package com.formdev.flatlaf.testing; import java.awt.*; import java.awt.Dialog.ModalityType; import java.awt.event.*; +import java.awt.image.BufferedImage; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Random; import javax.swing.*; import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.ui.FlatUIUtils; +import com.formdev.flatlaf.util.MultiResolutionImageSupport; import net.miginfocom.swing.*; /** @@ -355,6 +359,32 @@ public class FlatWindowDecorationsTest window.setIconImages( images ); else if( iconTestRandomRadioButton.isSelected() ) window.setIconImage( images.get( (int) (Math.random() * images.size()) ) ); + else if( iconTestMRIRadioButton.isSelected() ) { + ArrayList sortedImages = new ArrayList<>( images ); + sortedImages.sort( (image1, image2) -> { + return image1.getWidth( null ) - image2.getWidth( null ); + } ); + window.setIconImage( MultiResolutionImageSupport.create( 0, sortedImages.toArray( new Image[sortedImages.size()] ) ) ); + } else if( iconTestDynMRIRadioButton.isSelected() ) { + window.setIconImage( MultiResolutionImageSupport.create( 0, + new Dimension[] { + new Dimension( 16, 16 ), + }, dim -> { + BufferedImage image = new BufferedImage( dim.width, dim.height, BufferedImage.TYPE_INT_ARGB ); + Graphics2D g = image.createGraphics(); + try { + g.setColor( Color.getHSBColor( (dim.width - 16) / 64f, 1, 0.8f ) ); + g.fillRect( 0, 0, dim.width, dim.height ); + + g.setColor( Color.white ); + g.setFont( new Font( "Dialog", Font.PLAIN, (int) (dim.width * 0.8) ) ); + FlatUIUtils.drawString( this, g, String.valueOf( dim.width ), 0, dim.height - 2 ); + } finally { + g.dispose(); + } + return image; + } ) ); + } } private JRootPane getWindowRootPane() { @@ -403,6 +433,8 @@ public class FlatWindowDecorationsTest iconNoneRadioButton = new JRadioButton(); iconTestAllRadioButton = new JRadioButton(); iconTestRandomRadioButton = new JRadioButton(); + iconTestMRIRadioButton = new JRadioButton(); + iconTestDynMRIRadioButton = new JRadioButton(); JButton openDialogButton = new JButton(); JButton openFrameButton = new JButton(); menuBar = new JMenuBar(); @@ -642,6 +674,8 @@ public class FlatWindowDecorationsTest // rows "[]" + "[]" + + "[]" + + "[]" + "[]")); //---- iconNoneRadioButton ---- @@ -659,6 +693,16 @@ public class FlatWindowDecorationsTest iconTestRandomRadioButton.setText("test random"); iconTestRandomRadioButton.addActionListener(e -> iconChanged()); panel2.add(iconTestRandomRadioButton, "cell 0 2"); + + //---- iconTestMRIRadioButton ---- + iconTestMRIRadioButton.setText("test multi-resolution (Java 9+)"); + iconTestMRIRadioButton.addActionListener(e -> iconChanged()); + panel2.add(iconTestMRIRadioButton, "cell 0 3"); + + //---- iconTestDynMRIRadioButton ---- + iconTestDynMRIRadioButton.setText("test dynamic multi-resolution (Java 9+)"); + iconTestDynMRIRadioButton.addActionListener(e -> iconChanged()); + panel2.add(iconTestDynMRIRadioButton, "cell 0 4"); } add(panel2, "cell 1 8"); @@ -858,6 +902,8 @@ public class FlatWindowDecorationsTest iconButtonGroup.add(iconNoneRadioButton); iconButtonGroup.add(iconTestAllRadioButton); iconButtonGroup.add(iconTestRandomRadioButton); + iconButtonGroup.add(iconTestMRIRadioButton); + iconButtonGroup.add(iconTestDynMRIRadioButton); // JFormDesigner - End of component initialization //GEN-END:initComponents } @@ -892,6 +938,8 @@ public class FlatWindowDecorationsTest private JRadioButton iconNoneRadioButton; private JRadioButton iconTestAllRadioButton; private JRadioButton iconTestRandomRadioButton; + private JRadioButton iconTestMRIRadioButton; + private JRadioButton iconTestDynMRIRadioButton; 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 113f6d11..bc655f8e 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 @@ -328,7 +328,7 @@ new FormModel { } ) add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$columnConstraints": "[fill]" - "$rowConstraints": "[][][]" + "$rowConstraints": "[][][][][]" "$layoutConstraints": "ltr,insets 0,hidemode 3,gap 0 0" } ) { name: "panel2" @@ -366,6 +366,28 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 2" } ) + add( new FormComponent( "javax.swing.JRadioButton" ) { + name: "iconTestMRIRadioButton" + "text": "test multi-resolution (Java 9+)" + "$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 3" + } ) + add( new FormComponent( "javax.swing.JRadioButton" ) { + name: "iconTestDynMRIRadioButton" + "text": "test dynamic multi-resolution (Java 9+)" + "$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 4" + } ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 1 8" } ) @@ -386,7 +408,7 @@ new FormModel { } ) }, new FormLayoutConstraints( null ) { "location": new java.awt.Point( 0, 0 ) - "size": new java.awt.Dimension( 690, 440 ) + "size": new java.awt.Dimension( 690, 495 ) } ) add( new FormContainer( "javax.swing.JMenuBar", new FormLayoutManager( class javax.swing.JMenuBar ) ) { name: "menuBar" @@ -552,18 +574,18 @@ new FormModel { } ) } ) }, new FormLayoutConstraints( null ) { - "location": new java.awt.Point( 0, 470 ) + "location": new java.awt.Point( 0, 515 ) "size": new java.awt.Dimension( 255, 30 ) } ) add( new FormNonVisual( "javax.swing.ButtonGroup" ) { name: "styleButtonGroup" }, new FormLayoutConstraints( null ) { - "location": new java.awt.Point( 0, 520 ) + "location": new java.awt.Point( 0, 565 ) } ) add( new FormNonVisual( "javax.swing.ButtonGroup" ) { name: "iconButtonGroup" }, new FormLayoutConstraints( null ) { - "location": new java.awt.Point( 0, 570 ) + "location": new java.awt.Point( 0, 615 ) } ) } }