Drop shadows:

- reworked drop shadows implementation to support 4-sided shadows
- use 4-sided shadow for internal frames
- made shadows configurable in UI defaults
- made shadows dark in dark themes

(issue #94)
This commit is contained in:
Karl Tauber
2020-05-10 15:38:50 +02:00
parent 06cad7ecd8
commit 0a0f834f23
11 changed files with 250 additions and 43 deletions

View File

@@ -29,6 +29,12 @@ import com.formdev.flatlaf.util.UIScale;
/**
* Paints a drop shadow border around the component.
* Supports 1-sided, 2-side, 3-sided or 4-sided drop shadows.
* <p>
* The shadow insets allow specifying drop shadow thickness for each side.
* A zero or negative value hides the drop shadow on that side.
* A negative value can be used to indent the drop shadow on corners.
* E.g. -4 on left indents drop shadow at top-left and bottom-left corners by 4 pixels.
*
* @author Karl Tauber
*/
@@ -36,10 +42,10 @@ public class FlatDropShadowBorder
extends FlatEmptyBorder
{
private final Color shadowColor;
private final int shadowSize;
private final int cornerInset;
private final int shadowAlpha;
private final Insets shadowInsets;
private final float shadowOpacity;
private final int shadowSize;
private Image shadowImage;
private Color lastShadowColor;
private double lastSystemScaleFactor;
@@ -50,26 +56,36 @@ public class FlatDropShadowBorder
}
public FlatDropShadowBorder( Color shadowColor ) {
this( shadowColor, 4, 4, 128 );
this( shadowColor, 4, 0.5f );
}
public FlatDropShadowBorder( Color shadowColor, int shadowSize, int cornerInset, int shadowAlpha ) {
super( new Insets( 0, 0, shadowSize, shadowSize ) );
public FlatDropShadowBorder( Color shadowColor, int shadowSize, float shadowOpacity ) {
this( shadowColor, new Insets( -shadowSize, -shadowSize, shadowSize, shadowSize ), shadowOpacity );
}
public FlatDropShadowBorder( Color shadowColor, Insets shadowInsets, float shadowOpacity ) {
super( Math.max( shadowInsets.top, 0 ), Math.max( shadowInsets.left, 0 ),
Math.max( shadowInsets.bottom, 0 ), Math.max( shadowInsets.right, 0 ) );
this.shadowColor = shadowColor;
this.shadowSize = shadowSize;
this.cornerInset = cornerInset;
this.shadowAlpha = shadowAlpha;
this.shadowInsets = shadowInsets;
this.shadowOpacity = shadowOpacity;
shadowSize = Math.max(
Math.max( shadowInsets.left, shadowInsets.right ),
Math.max( shadowInsets.top, shadowInsets.bottom ) );
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
if( shadowSize <= 0 )
return;
HiDPIUtils.paintAtScale1x( (Graphics2D) g, x, y, width, height, this::paintImpl );
}
private void paintImpl( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
Color shadowColor = (this.shadowColor != null) ? this.shadowColor : g.getColor();
int shadowSize = (int) Math.ceil( UIScale.scale( this.shadowSize ) * scaleFactor );
int cornerInset = (int) Math.ceil( UIScale.scale( this.cornerInset ) * scaleFactor );
int shadowSize = scale( this.shadowSize, scaleFactor );
// create and cache shadow image
float userScaleFactor = UIScale.getUserScaleFactor();
@@ -78,7 +94,7 @@ public class FlatDropShadowBorder
lastSystemScaleFactor != scaleFactor ||
lastUserScaleFactor != userScaleFactor )
{
shadowImage = createShadowImage( shadowColor, shadowSize, shadowAlpha,
shadowImage = createShadowImage( shadowColor, shadowSize, shadowOpacity,
(float) (scaleFactor * userScaleFactor) );
lastShadowColor = shadowColor;
lastSystemScaleFactor = scaleFactor;
@@ -87,55 +103,103 @@ public class FlatDropShadowBorder
/*debug
int m = shadowImage.getWidth( null );
Color oldColor = g.getColor();
g.setColor( Color.lightGray );
g.drawRect( x - m - 1, y - m - 1, m + 1, m + 1 );
g.setColor( Color.white );
g.fillRect( x - m, y - m, m, m );
g.drawImage( shadowImage, x - m, y - m, null );
g.setColor( oldColor );
debug*/
int x1c = x + cornerInset;
int y1c = y + cornerInset;
int x1cs = x1c + shadowSize;
int y1cs = y1c + shadowSize;
int left = scale( shadowInsets.left, scaleFactor );
int right = scale( shadowInsets.right, scaleFactor );
int top = scale( shadowInsets.top, scaleFactor );
int bottom = scale( shadowInsets.bottom, scaleFactor );
int x2s = x + width;
int y2s = y + height;
int x2 = x2s - shadowSize;
int y2 = y2s - shadowSize;
// shadow outer coordinates
int x1o = x - Math.min( left, 0 );
int y1o = y - Math.min( top, 0 );
int x2o = x + width + Math.min( right, 0 );
int y2o = y + height + Math.min( bottom, 0 );
// shadow inner coordinates
int x1i = x1o + shadowSize;
int y1i = y1o + shadowSize;
int x2i = x2o - shadowSize;
int y2i = y2o - shadowSize;
int wh = (shadowSize * 2) - 1;
int center = shadowSize - 1;
// left-bottom edge
g.drawImage( shadowImage, x1c, y2, x1cs, y2s,
0, center, shadowSize, wh, null );
// left-top edge
if( left > 0 || top > 0 ) {
g.drawImage( shadowImage, x1o, y1o, x1i, y1i,
0, 0, center, center, null );
}
// bottom shadow
g.drawImage( shadowImage, x1cs, y2, x2, y2s,
center, center, center + 1, wh, null );
// right-bottom edge
g.drawImage( shadowImage, x2, y2, x2s, y2s,
center, center, wh, wh, null );
// right shadow
g.drawImage( shadowImage, x2, y1cs, x2s, y2,
center, center, wh, center + 1, null );
// top shadow
if( top > 0 ) {
g.drawImage( shadowImage, x1i, y1o, x2i, y1i,
center, 0, center + 1, center, null );
}
// right-top edge
g.drawImage( shadowImage, x2, y1c, x2s, y1cs,
center, 0, wh, shadowSize, null );
if( right > 0 || top > 0 ) {
g.drawImage( shadowImage, x2i, y1o, x2o, y1i,
center, 0, wh, center, null );
}
// left shadow
if( left > 0 ) {
g.drawImage( shadowImage, x1o, y1i, x1i, y2i,
0, center, center, center + 1, null );
}
// right shadow
if( right > 0 ) {
g.drawImage( shadowImage, x2i, y1i, x2o, y2i,
center, center, wh, center + 1, null );
}
// left-bottom edge
if( left > 0 || bottom > 0 ) {
g.drawImage( shadowImage, x1o, y2i, x1i, y2o,
0, center, center, wh, null );
}
// bottom shadow
if( bottom > 0 ) {
g.drawImage( shadowImage, x1i, y2i, x2i, y2o,
center, center, center + 1, wh, null );
}
// right-bottom edge
if( right > 0 || bottom > 0 ) {
g.drawImage( shadowImage, x2i, y2i, x2o, y2o,
center, center, wh, wh, null );
}
}
private int scale( int value, double scaleFactor ) {
return (int) Math.ceil( UIScale.scale( value ) * scaleFactor );
}
private static BufferedImage createShadowImage( Color shadowColor, int shadowSize,
int shadowAlpha, float scaleFactor )
float shadowOpacity, float scaleFactor )
{
int shadowRGB = shadowColor.getRGB() & 0xffffff;
int shadowAlpha = (int) (255 * shadowOpacity);
Color startColor = new Color( shadowRGB | ((shadowAlpha & 0xff) << 24), true );
Color midColor = new Color( shadowRGB | (((shadowAlpha / 2) & 0xff) << 24), true );
Color endColor = new Color( shadowRGB, true );
/*debug
startColor = Color.red;
midColor = Color.green;
endColor = Color.blue;
debug*/
int wh = (shadowSize * 2) - 1;
int center = shadowSize - 1;

View File

@@ -112,8 +112,16 @@ public class FlatInternalFrameUI
private final Color activeBorderColor = UIManager.getColor( "InternalFrame.activeBorderColor" );
private final Color inactiveBorderColor = UIManager.getColor( "InternalFrame.inactiveBorderColor" );
private final int borderLineWidth = FlatUIUtils.getUIInt( "InternalFrame.borderLineWidth", 1 );
private final boolean dropShadowPainted = UIManager.getBoolean( "InternalFrame.dropShadowPainted" );
private final FlatDropShadowBorder dropShadowBorder = new FlatDropShadowBorder();
private final FlatDropShadowBorder activeDropShadowBorder = new FlatDropShadowBorder(
UIManager.getColor( "InternalFrame.activeDropShadowColor" ),
UIManager.getInsets( "InternalFrame.activeDropShadowInsets" ),
FlatUIUtils.getUIFloat( "InternalFrame.activeDropShadowOpacity", 0.5f ) );
private final FlatDropShadowBorder inactiveDropShadowBorder = new FlatDropShadowBorder(
UIManager.getColor( "InternalFrame.inactiveDropShadowColor" ),
UIManager.getInsets( "InternalFrame.inactiveDropShadowInsets" ),
FlatUIUtils.getUIFloat( "InternalFrame.inactiveDropShadowOpacity", 0.5f ) );
public FlatInternalFrameBorder() {
super( UIManager.getInsets( "InternalFrame.borderMargins" ) );
@@ -150,10 +158,17 @@ public class FlatInternalFrameUI
g2.setColor( f.isSelected() ? activeBorderColor : inactiveBorderColor );
// paint drop shadow
Insets dropShadowInsets = dropShadowBorder.getBorderInsets();
dropShadowBorder.paintBorder( c, g2, (int) rx, (int) ry,
(int) rwidth + dropShadowInsets.right,
(int) rheight + dropShadowInsets.bottom );
if( dropShadowPainted ) {
FlatDropShadowBorder dropShadowBorder = f.isSelected()
? activeDropShadowBorder : inactiveDropShadowBorder;
Insets dropShadowInsets = dropShadowBorder.getBorderInsets();
dropShadowBorder.paintBorder( c, g2,
(int) rx - dropShadowInsets.left,
(int) ry - dropShadowInsets.top,
(int) rwidth + dropShadowInsets.left + dropShadowInsets.right,
(int) rheight + dropShadowInsets.top + dropShadowInsets.bottom );
}
// paint border
g2.fill( FlatUIUtils.createRectangle( rx, ry, rwidth, rheight, lineWidth ) );

View File

@@ -29,6 +29,7 @@ import javax.swing.JComponent;
import javax.swing.Popup;
import javax.swing.PopupFactory;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import com.formdev.flatlaf.util.SystemInfo;
@@ -49,6 +50,9 @@ public class FlatPopupFactory
public Popup getPopup( Component owner, Component contents, int x, int y )
throws IllegalArgumentException
{
if( !UIManager.getBoolean( "Popup.dropShadowPainted" ) && !SystemInfo.IS_MAC )
return super.getPopup( owner, contents, x, y );
// 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 );
@@ -129,7 +133,10 @@ public class FlatPopupFactory
parent = (JComponent) p;
oldBorder = parent.getBorder();
oldOpaque = parent.isOpaque();
parent.setBorder( new FlatDropShadowBorder( null, 4, 4, 32 ) ); //TODO
parent.setBorder( new FlatDropShadowBorder(
UIManager.getColor( "Popup.dropShadowColor" ),
UIManager.getInsets( "Popup.dropShadowInsets" ),
FlatUIUtils.getUIFloat( "Popup.dropShadowOpacity", 0.5f ) ) );
parent.setOpaque( false );
window = SwingUtilities.windowForComponent( contents );

View File

@@ -158,6 +158,9 @@ InternalFrame.closePressedBackground=darken(Actions.Red,10%,lazy)
InternalFrame.closeHoverForeground=#fff
InternalFrame.closePressedForeground=#fff
InternalFrame.activeDropShadowOpacity=0.5
InternalFrame.inactiveDropShadowOpacity=0.75
#---- List ----
@@ -187,6 +190,12 @@ MenuItemCheckBox.icon.disabledCheckmarkColor=#606060
PasswordField.capsLockIconColor=#ffffff64
#---- Popup ----
Popup.dropShadowColor=#000
Popup.dropShadowOpacity=0.25
#---- PopupMenu ----
PopupMenu.borderColor=#5e5e5e

View File

@@ -261,6 +261,13 @@ InternalFrame.maximizeIcon=com.formdev.flatlaf.icons.FlatInternalFrameMaximizeIc
InternalFrame.minimizeIcon=com.formdev.flatlaf.icons.FlatInternalFrameMinimizeIcon
InternalFrame.windowBindings=null
# drop shadow
InternalFrame.dropShadowPainted=true
InternalFrame.activeDropShadowColor=null
InternalFrame.activeDropShadowInsets=5,5,6,6
InternalFrame.inactiveDropShadowColor=null
InternalFrame.inactiveDropShadowInsets=3,3,4,4
#---- InternalFrameTitlePane ----
@@ -362,6 +369,12 @@ PasswordField.echoChar=\u2022
PasswordField.capsLockIcon=com.formdev.flatlaf.icons.FlatCapsLockIcon
#---- Popup ----
Popup.dropShadowPainted=true
Popup.dropShadowInsets=-4,-4,4,4
#---- PopupMenu ----
PopupMenu.border=com.formdev.flatlaf.ui.FlatPopupMenuBorder

View File

@@ -165,6 +165,9 @@ InternalFrame.closePressedBackground=darken(Actions.Red,10%,lazy)
InternalFrame.closeHoverForeground=#fff
InternalFrame.closePressedForeground=#fff
InternalFrame.activeDropShadowOpacity=0.25
InternalFrame.inactiveDropShadowOpacity=0.5
#---- List ----
@@ -194,6 +197,12 @@ MenuItemCheckBox.icon.disabledCheckmarkColor=#ABABAB
PasswordField.capsLockIconColor=#00000064
#---- Popup ----
Popup.dropShadowColor=#000
Popup.dropShadowOpacity=0.15
#---- PopupMenu ----
PopupMenu.borderColor=#adadad