From 465dc8a66c31ddada0ff1fb7673718288d25809c Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Fri, 8 May 2020 11:02:20 +0200 Subject: [PATCH] Popup: added drop shadows to all popups (menu, combobox and tooltip) on all platforms (issue #94) --- .../java/com/formdev/flatlaf/FlatLaf.java | 12 ++ .../formdev/flatlaf/ui/FlatPopupFactory.java | 168 ++++++++++++++++++ .../formdev/flatlaf/ui/FlatPopupMenuUI.java | 22 --- 3 files changed, 180 insertions(+), 22 deletions(-) create mode 100644 flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupFactory.java 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 3ec42a7a..cfb010e7 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java @@ -56,6 +56,7 @@ import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicLookAndFeel; import javax.swing.text.StyleContext; import javax.swing.text.html.HTMLEditorKit; +import com.formdev.flatlaf.ui.FlatPopupFactory; import com.formdev.flatlaf.util.GrayFilter; import com.formdev.flatlaf.util.MultiResolutionImageSupport; import com.formdev.flatlaf.util.SystemInfo; @@ -78,6 +79,7 @@ public abstract class FlatLaf private static boolean aquaLoaded; private static boolean updateUIPending; + private PopupFactory oldPopupFactory; private MnemonicHandler mnemonicHandler; private Consumer postInitialization; @@ -144,6 +146,10 @@ public abstract class FlatLaf super.initialize(); + // install popup factory + oldPopupFactory = PopupFactory.getSharedInstance(); + PopupFactory.setSharedInstance( new FlatPopupFactory() ); + // install mnemonic handler mnemonicHandler = new MnemonicHandler(); mnemonicHandler.install(); @@ -200,6 +206,12 @@ public abstract class FlatLaf desktopPropertyListener = null; } + // uninstall popup factory + if( oldPopupFactory != null ) { + PopupFactory.setSharedInstance( oldPopupFactory ); + oldPopupFactory = null; + } + // uninstall mnemonic handler if( mnemonicHandler != null ) { mnemonicHandler.uninstall(); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupFactory.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupFactory.java new file mode 100644 index 00000000..8790c267 --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupFactory.java @@ -0,0 +1,168 @@ +/* + * 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.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Window; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import javax.swing.JComponent; +import javax.swing.Popup; +import javax.swing.PopupFactory; +import javax.swing.SwingUtilities; +import javax.swing.border.Border; +import com.formdev.flatlaf.util.SystemInfo; + +/** + * A popup factory that adds drop shadows to popups on Windows and Linux. + * On macOS, heavy weight popups (without drop shadow) are produced and the + * operating system automatically adds drop shadows. + * + * @author Karl Tauber + */ +public class FlatPopupFactory + extends PopupFactory +{ + private Method java8getPopupMethod; + private Method java9getPopupMethod; + + @Override + public Popup getPopup( Component owner, Component contents, int x, int y ) + throws IllegalArgumentException + { + // always use heavy weight popup because the drop shadow increases + // the popup size and may overlap the window bounds + Popup popup = getHeavyWeightPopup( owner, contents, x, y ); + + // failed to get heavy weight popup --> do not add drop shadow + if( popup == null ) + return super.getPopup( owner, contents, x, y ); + + // macOS adds drop shadow to heavy weight popups + if( SystemInfo.IS_MAC ) + return popup; + + // create drop shadow popup + return new DropShadowPopup( popup, contents ); + } + + /** + * There is no API in Java 8 to force creation of heavy weight popups, + * but it is possible with reflection. Java 9 provides a new method. + * + * When changing FlatLaf system requirements to Java 9+, + * then this method can be replaced with: + * return getPopup( owner, contents, x, y, true ); + */ + private Popup getHeavyWeightPopup( Component owner, Component contents, int x, int y ) + throws IllegalArgumentException + { + try { + if( SystemInfo.IS_JAVA_9_OR_LATER ) { + if( java9getPopupMethod == null ) { + java9getPopupMethod = PopupFactory.class.getDeclaredMethod( + "getPopup", Component.class, Component.class, int.class, int.class, boolean.class ); + } + return (Popup) java9getPopupMethod.invoke( this, owner, contents, x, y, true ); + } else { + // Java 8 + if( java8getPopupMethod == null ) { + java8getPopupMethod = PopupFactory.class.getDeclaredMethod( + "getPopup", Component.class, Component.class, int.class, int.class, int.class ); + java8getPopupMethod.setAccessible( true ); + } + return (Popup) java8getPopupMethod.invoke( this, owner, contents, x, y, /*HEAVY_WEIGHT_POPUP*/ 2 ); + } + } catch( NoSuchMethodException | SecurityException | IllegalAccessException | InvocationTargetException ex ) { + // ignore + return null; + } + } + + //---- class DropShadowPopup ---------------------------------------------- + + private static class DropShadowPopup + extends Popup + { + private Popup delegate; + + private JComponent parent; + private Border oldBorder; + private boolean oldOpaque; + + private Window window; + private Color oldBackground; + + DropShadowPopup( Popup delegate, Component contents ) { + this.delegate = delegate; + + if( delegate.getClass().getName().endsWith( "MediumWeightPopup" ) ) + return; + + Dimension size = contents.getPreferredSize(); + if( size.width <= 0 || size.height <= 0 ) + return; + + Container p = contents.getParent(); + if( !(p instanceof JComponent) ) + return; + + parent = (JComponent) p; + oldBorder = parent.getBorder(); + oldOpaque = parent.isOpaque(); + parent.setBorder( new FlatDropShadowBorder( null, 4, 4, 32 ) ); //TODO + parent.setOpaque( false ); + + window = SwingUtilities.windowForComponent( contents ); + if( window != null ) { + oldBackground = window.getBackground(); + window.setBackground( new Color( 0, true ) ); + window.setSize( window.getPreferredSize() ); + } else + parent.setSize( parent.getPreferredSize() ); + } + + @Override + public void show() { + delegate.show(); + } + + @Override + public void hide() { + if( delegate == null ) + return; + + delegate.hide(); + + if( parent != null ) { + parent.setBorder( oldBorder ); + parent.setOpaque( oldOpaque ); + parent = null; + } + + if( window != null ) { + window.setBackground( oldBackground ); + window = null; + } + + delegate = null; + } + } +} diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupMenuUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupMenuUI.java index 1fae8212..480b0222 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupMenuUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupMenuUI.java @@ -19,7 +19,6 @@ package com.formdev.flatlaf.ui; import javax.swing.JComponent; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicPopupMenuUI; -import com.formdev.flatlaf.util.SystemInfo; /** * Provides the Flat LaF UI delegate for {@link javax.swing.JPopupMenu}. @@ -36,28 +35,7 @@ import com.formdev.flatlaf.util.SystemInfo; public class FlatPopupMenuUI extends BasicPopupMenuUI { - private boolean oldLightWeightPopupEnabled; - public static ComponentUI createUI( JComponent c ) { return new FlatPopupMenuUI(); } - - @Override - public void installDefaults() { - super.installDefaults(); - - // use heavy-weight popups on macOS to get nice drop shadow from OS - if( SystemInfo.IS_MAC ) { - oldLightWeightPopupEnabled = popupMenu.isLightWeightPopupEnabled(); - popupMenu.setLightWeightPopupEnabled( false ); - } - } - - @Override - protected void uninstallDefaults() { - super.uninstallDefaults(); - - if( SystemInfo.IS_MAC ) - popupMenu.setLightWeightPopupEnabled( oldLightWeightPopupEnabled ); - } }