From c9c703fe9844d5075689d82152a87cb4f5ae2d29 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Fri, 24 Apr 2020 17:07:30 +0200 Subject: [PATCH] support multi-resolution images in disabled icons on Java 9+ (e.g. @2x icons on macOS) (issue #70) --- flatlaf-core/build.gradle.kts | 11 ++ .../java/com/formdev/flatlaf/FlatLaf.java | 11 +- .../util/MultiResolutionImageSupport.java | 44 ++++++++ .../util/MultiResolutionImageSupport.java | 96 ++++++++++++++++++ flatlaf-demo/build.gradle.kts | 3 + .../flatlaf/demo/MoreComponentsPanel.java | 6 ++ .../flatlaf/demo/MoreComponentsPanel.jfd | 7 +- .../demo/icons/intellij-showWriteAccess.png | Bin 0 -> 325 bytes .../icons/intellij-showWriteAccess@2x.png | Bin 0 -> 495 bytes .../testing/FlatDisabledIconsTest.java | 22 +++- 10 files changed, 195 insertions(+), 5 deletions(-) create mode 100644 flatlaf-core/src/main/java/com/formdev/flatlaf/util/MultiResolutionImageSupport.java create mode 100644 flatlaf-core/src/main/java9/com/formdev/flatlaf/util/MultiResolutionImageSupport.java create mode 100644 flatlaf-demo/src/main/resources/com/formdev/flatlaf/demo/icons/intellij-showWriteAccess.png create mode 100644 flatlaf-demo/src/main/resources/com/formdev/flatlaf/demo/icons/intellij-showWriteAccess@2x.png diff --git a/flatlaf-core/build.gradle.kts b/flatlaf-core/build.gradle.kts index c66bd8f5..c3a6d17a 100644 --- a/flatlaf-core/build.gradle.kts +++ b/flatlaf-core/build.gradle.kts @@ -23,6 +23,11 @@ plugins { if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) { sourceSets { + create( "java9" ) { + java { + setSrcDirs( listOf( "src/main/java9" ) ) + } + } create( "module-info" ) { java { // include "src/main/java" here to get compile errors if classes are @@ -52,6 +57,12 @@ tasks { archiveBaseName.set( "flatlaf" ) if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) { + manifest.attributes( "Multi-Release" to "true" ) + + into( "META-INF/versions/9" ) { + from( sourceSets["java9"].output ) + } + from( sourceSets["module-info"].output ) { include( "module-info.class" ) } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java index 4f4e6b30..383b5034 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java @@ -41,6 +41,7 @@ import java.util.Map; import java.util.Properties; import java.util.ServiceLoader; import java.util.function.Consumer; +import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.AbstractButton; @@ -64,6 +65,7 @@ import javax.swing.plaf.basic.BasicLookAndFeel; import javax.swing.text.StyleContext; import javax.swing.text.html.HTMLEditorKit; import com.formdev.flatlaf.util.GrayFilter; +import com.formdev.flatlaf.util.MultiResolutionImageSupport; import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.UIScale; @@ -136,9 +138,14 @@ public abstract class FlatLaf : new GrayFilter( 25, -25, 100 ); } + ImageFilter filter = (ImageFilter) grayFilter; + Function mapper = img -> { + ImageProducer producer = new FilteredImageSource( img.getSource(), filter ); + return Toolkit.getDefaultToolkit().createImage( producer ); + }; + Image image = ((ImageIcon)icon).getImage(); - ImageProducer producer = new FilteredImageSource( image.getSource(), (ImageFilter) grayFilter ); - return new ImageIconUIResource( Toolkit.getDefaultToolkit().createImage( producer ) ); + return new ImageIconUIResource( MultiResolutionImageSupport.map( image, mapper ) ); } return null; diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/MultiResolutionImageSupport.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/MultiResolutionImageSupport.java new file mode 100644 index 00000000..a250c2ed --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/MultiResolutionImageSupport.java @@ -0,0 +1,44 @@ +/* + * 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.util; + +import java.awt.Image; +import java.util.function.Function; + +/** + * Support for multi-resolution images available since Java 9. + * + * @author Karl Tauber + */ +public class MultiResolutionImageSupport +{ + public static boolean isAvailable() { + return false; + } + + public static boolean isMultiResolutionImage( Image image ) { + return false; + } + + public static Image create( int baseImageIndex, Image... resolutionVariants ) { + return resolutionVariants[baseImageIndex]; + } + + public static Image map( Image image, Function mapper ) { + return mapper.apply( image ); + } +} diff --git a/flatlaf-core/src/main/java9/com/formdev/flatlaf/util/MultiResolutionImageSupport.java b/flatlaf-core/src/main/java9/com/formdev/flatlaf/util/MultiResolutionImageSupport.java new file mode 100644 index 00000000..8865c2e1 --- /dev/null +++ b/flatlaf-core/src/main/java9/com/formdev/flatlaf/util/MultiResolutionImageSupport.java @@ -0,0 +1,96 @@ +/* + * 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.util; + +import java.awt.Image; +import java.awt.image.AbstractMultiResolutionImage; +import java.awt.image.BaseMultiResolutionImage; +import java.awt.image.MultiResolutionImage; +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.function.Function; +import javax.swing.ImageIcon; + +/** + * Support for multi-resolution images available since Java 9. + * + * @author Karl Tauber + */ +public class MultiResolutionImageSupport +{ + public static boolean isAvailable() { + return true; + } + + public static boolean isMultiResolutionImage( Image image ) { + return image instanceof MultiResolutionImage; + } + + public static Image create( int baseImageIndex, Image... resolutionVariants ) { + return new BaseMultiResolutionImage( baseImageIndex, resolutionVariants ); + } + + public static Image map( Image image, Function mapper ) { + return image instanceof MultiResolutionImage + ? new MappedMultiResolutionImage( image, mapper ) + : mapper.apply( image ); + } + + //---- class MappedMultiResolutionImage ----------------------------------- + + private static class MappedMultiResolutionImage + extends AbstractMultiResolutionImage + { + private final Image mrImage; + private final Function mapper; + private final IdentityHashMap cache = new IdentityHashMap<>(); + + MappedMultiResolutionImage( Image mrImage, Function mapper ) { + assert mrImage instanceof MultiResolutionImage; + + this.mrImage = mrImage; + this.mapper = mapper; + } + + @Override + public Image getResolutionVariant( double destImageWidth, double destImageHeight ) { + Image variant = ((MultiResolutionImage)mrImage).getResolutionVariant( destImageWidth, destImageHeight ); + return mapAndCacheImage( variant ); + } + + @Override + public List getResolutionVariants() { + List variants = ((MultiResolutionImage)mrImage).getResolutionVariants(); + List mappedVariants = new ArrayList<>(); + for( Image image : variants ) + mappedVariants.add( mapAndCacheImage( image ) ); + return mappedVariants; + } + + @Override + protected Image getBaseImage() { + return mapAndCacheImage( mrImage ); + } + + private Image mapAndCacheImage( Image image ) { + return cache.computeIfAbsent( image, img -> { + return new ImageIcon( mapper.apply( img ) ).getImage(); + } ); + } + } +} diff --git a/flatlaf-demo/build.gradle.kts b/flatlaf-demo/build.gradle.kts index 44bd2a84..86674d04 100644 --- a/flatlaf-demo/build.gradle.kts +++ b/flatlaf-demo/build.gradle.kts @@ -43,6 +43,9 @@ tasks { manifest { attributes( "Main-Class" to "com.formdev.flatlaf.demo.FlatLafDemo" ) + + if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) + attributes( "Multi-Release" to "true" ) } exclude( "module-info.class" ) diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/MoreComponentsPanel.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/MoreComponentsPanel.java index d0a1b198..3d69c83e 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/MoreComponentsPanel.java +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/MoreComponentsPanel.java @@ -101,6 +101,7 @@ class MoreComponentsPanel JButton button7 = new JButton(); JButton button8 = new JButton(); JToggleButton toggleButton6 = new JToggleButton(); + JButton button1 = new JButton(); //======== this ======== setLayout(new MigLayout( @@ -380,6 +381,11 @@ class MoreComponentsPanel toggleButton6.setIcon(UIManager.getIcon("Tree.leafIcon")); toggleButton6.setSelected(true); toolBar1.add(toggleButton6); + + //---- button1 ---- + button1.setIcon(new ImageIcon(getClass().getResource("/com/formdev/flatlaf/demo/icons/intellij-showWriteAccess.png"))); + button1.setEnabled(false); + toolBar1.add(button1); } add(toolBar1, "cell 1 10 3 1,growx"); // JFormDesigner - End of component initialization //GEN-END:initComponents diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/MoreComponentsPanel.jfd b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/MoreComponentsPanel.jfd index f533a270..0ea97a02 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/MoreComponentsPanel.jfd +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/MoreComponentsPanel.jfd @@ -1,4 +1,4 @@ -JFDML JFormDesigner: "7.0.0.0.194" Java: "11.0.2" encoding: "UTF-8" +JFDML JFormDesigner: "7.0.1.0.272" Java: "13.0.2" encoding: "UTF-8" new FormModel { contentType: "form/swing" @@ -355,6 +355,11 @@ new FormModel { "icon": new com.jformdesigner.model.SwingIcon( 2, "Tree.leafIcon" ) "selected": true } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "button1" + "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/intellij-showWriteAccess.png" ) + "enabled": false + } ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 1 10 3 1,growx" } ) diff --git a/flatlaf-demo/src/main/resources/com/formdev/flatlaf/demo/icons/intellij-showWriteAccess.png b/flatlaf-demo/src/main/resources/com/formdev/flatlaf/demo/icons/intellij-showWriteAccess.png new file mode 100644 index 0000000000000000000000000000000000000000..c006c56f9ae4293deebb064c036688b05ef9f945 GIT binary patch literal 325 zcmV-L0lNN)P)mUPe zMJ6mKX!Mv5atMz9d@Cx!fG(C_ z4b9>KT6!H}IouJC?}sKx(A4t%4h<|oi#B6g4tK=q`w0mgG>rH7$yhA?jmBS1H4BT2 zi$%~99jfKw3H?A^fi8Y0BEFd_4#>;PJCE#$TM>yha7!ZNcY~q?oE;+)=3_W6GBJi4 z4)~A55!ZonEr6Of682+6O?ZL@H5`E92xQCQV!#}Lnl-_Wczq`{S%n%7z;Fbi!U+jq z$L3&c77&98I|3N@7>N=oy-?}^q8tIr1(-P_VfuhN;8tY9aymu=bMoU8dNTb5;A}WUnZs?x)q-2L)D`A zPIzJtB7Wmn!?M=x@c7vnsUE17s+G~*$Rr!|#C0noz8hO*1QMrJb-b8Rbqp$WXx1ix z#`SSL4U)?^i(P7gn~`ZEK#Oi;Ym$N5igalgg0dDc?%yLh;5#Ud(6wcJD=f(c5&wxH l1JXBulnDt9qg}Ki2LSf*ka5I|9rFMH002ovPDHLkV1np6(V+kU literal 0 HcmV?d00001 diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatDisabledIconsTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatDisabledIconsTest.java index c4f0dfd3..e375530d 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatDisabledIconsTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatDisabledIconsTest.java @@ -27,8 +27,11 @@ import java.awt.image.FilteredImageSource; import java.awt.image.ImageProducer; import java.awt.image.RGBImageFilter; import java.beans.*; +import java.net.URL; import javax.swing.*; import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.util.MultiResolutionImageSupport; +import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.UIScale; import net.miginfocom.swing.*; @@ -575,8 +578,23 @@ public class FlatDisabledIconsTest private final ImageIcon darkIcon; LightOrDarkIcon( String lightIconName, String darkIconName ) { - this.lightIcon = new ImageIcon( getClass().getResource( lightIconName ) ); - this.darkIcon = new ImageIcon( getClass().getResource( darkIconName ) ); + this.lightIcon = loadIcon( lightIconName ); + this.darkIcon = loadIcon( darkIconName ); + } + + private static ImageIcon loadIcon( String iconName ) { + ImageIcon icon = new ImageIcon( LightOrDarkIcon.class.getResource( iconName ) ); + + if( SystemInfo.IS_MAC || !MultiResolutionImageSupport.isAvailable() || !iconName.endsWith( ".png" ) ) + return icon; + + String iconName2x = iconName.replace( ".png", "@2x.png" ); + URL url2x = LightOrDarkIcon.class.getResource( iconName2x ); + if( url2x == null ) + return icon; + + ImageIcon icon2x = new ImageIcon( url2x ); + return new ImageIcon( MultiResolutionImageSupport.create( 0, icon.getImage(), icon2x.getImage() ) ); } private ImageIcon getCurrentIcon() {