Skip to content

Commit

Permalink
Popup: added drop shadows to all popups (menu, combobox and tooltip) …
Browse files Browse the repository at this point in the history
…on all platforms (issue #94)
  • Loading branch information
DevCharly committed May 8, 2020
1 parent 0a181f6 commit 465dc8a
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 22 deletions.
12 changes: 12 additions & 0 deletions flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
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;
Expand All @@ -78,6 +79,7 @@ public abstract class FlatLaf
private static boolean aquaLoaded;
private static boolean updateUIPending;

private PopupFactory oldPopupFactory;
private MnemonicHandler mnemonicHandler;

private Consumer<UIDefaults> postInitialization;
Expand Down Expand Up @@ -144,6 +146,10 @@ public void initialize() {

super.initialize();

// install popup factory
oldPopupFactory = PopupFactory.getSharedInstance();
PopupFactory.setSharedInstance( new FlatPopupFactory() );

// install mnemonic handler
mnemonicHandler = new MnemonicHandler();
mnemonicHandler.install();
Expand Down Expand Up @@ -200,6 +206,12 @@ public void uninitialize() {
desktopPropertyListener = null;
}

// uninstall popup factory
if( oldPopupFactory != null ) {
PopupFactory.setSharedInstance( oldPopupFactory );
oldPopupFactory = null;
}

// uninstall mnemonic handler
if( mnemonicHandler != null ) {
mnemonicHandler.uninstall();
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
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}.
Expand All @@ -36,28 +35,7 @@
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 );
}
}

0 comments on commit 465dc8a

Please sign in to comment.