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 00000000..c006c56f Binary files /dev/null and b/flatlaf-demo/src/main/resources/com/formdev/flatlaf/demo/icons/intellij-showWriteAccess.png differ diff --git a/flatlaf-demo/src/main/resources/com/formdev/flatlaf/demo/icons/intellij-showWriteAccess@2x.png b/flatlaf-demo/src/main/resources/com/formdev/flatlaf/demo/icons/intellij-showWriteAccess@2x.png new file mode 100644 index 00000000..b1a2398e Binary files /dev/null and b/flatlaf-demo/src/main/resources/com/formdev/flatlaf/demo/icons/intellij-showWriteAccess@2x.png differ 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() {