From df905a1d7349288e83b91587119470b58576f6d3 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Tue, 16 Nov 2021 10:49:53 +0100 Subject: [PATCH] Demo: made hint popups look nicer (balloon with arrow and shadow) --- .../com/formdev/flatlaf/demo/HintManager.java | 161 ++++++++++++++++-- 1 file changed, 148 insertions(+), 13 deletions(-) diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/HintManager.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/HintManager.java index 19d59477..50a4b54e 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/HintManager.java +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/HintManager.java @@ -18,13 +18,15 @@ package com.formdev.flatlaf.demo; import java.awt.*; import java.awt.event.MouseAdapter; +import java.awt.geom.Area; +import java.awt.geom.RoundRectangle2D; import java.util.ArrayList; import java.util.List; import javax.swing.*; -import javax.swing.border.LineBorder; +import javax.swing.border.Border; import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.ui.FlatDropShadowBorder; -import com.formdev.flatlaf.ui.FlatPopupMenuBorder; +import com.formdev.flatlaf.ui.FlatEmptyBorder; import com.formdev.flatlaf.ui.FlatUIUtils; import com.formdev.flatlaf.util.UIScale; import net.miginfocom.swing.*; @@ -89,6 +91,9 @@ class HintManager initComponents(); + setOpaque( false ); + updateBalloonBorder(); + hintLabel.setText( "" + hint.message + "" ); // grab all mouse events to avoid that components overlapped @@ -100,14 +105,28 @@ class HintManager public void updateUI() { super.updateUI(); - if( UIManager.getLookAndFeel() instanceof FlatLaf ) { + if( UIManager.getLookAndFeel() instanceof FlatLaf ) setBackground( UIManager.getColor( "HintPanel.backgroundColor" ) ); - setBorder( new FlatPopupMenuBorder() ); - } else { + else { // using nonUIResource() because otherwise Nimbus does not fill the background setBackground( FlatUIUtils.nonUIResource( UIManager.getColor( "info" ) ) ); - setBorder( new LineBorder( Color.gray ) ); } + + if( hint != null ) + updateBalloonBorder(); + } + + private void updateBalloonBorder() { + int direction; + switch( hint.position ) { + case SwingConstants.LEFT: direction = SwingConstants.RIGHT; break; + case SwingConstants.TOP: direction = SwingConstants.BOTTOM; break; + case SwingConstants.RIGHT: direction = SwingConstants.LEFT; break; + case SwingConstants.BOTTOM: direction = SwingConstants.TOP; break; + default: throw new IllegalArgumentException(); + } + + setBorder( new BalloonBorder( direction, FlatUIUtils.getUIColor( "PopupMenu.borderColor", Color.gray ) ) ); } void showHint() { @@ -123,13 +142,6 @@ class HintManager public void updateUI() { super.updateUI(); - if( UIManager.getLookAndFeel() instanceof FlatLaf ) { - setBorder( new FlatDropShadowBorder( - UIManager.getColor( "Popup.dropShadowColor" ), - UIManager.getInsets( "Popup.dropShadowInsets" ), - FlatUIUtils.getUIFloat( "Popup.dropShadowOpacity", 0.5f ) ) ); - } - // use invokeLater because at this time the UI delegates // of child components are not yet updated EventQueue.invokeLater( () -> { @@ -226,4 +238,127 @@ class HintManager private JButton gotItButton; // JFormDesigner - End of variables declaration //GEN-END:variables } + + //---- class BalloonBorder ------------------------------------------------ + + private static class BalloonBorder + extends FlatEmptyBorder + { + private static int ARC = 8; + private static int ARROW_XY = 16; + private static int ARROW_SIZE = 8; + private static int SHADOW_SIZE = 6; + private static int SHADOW_TOP_SIZE = 3; + private static int SHADOW_SIZE2 = SHADOW_SIZE + 2; + + private final int direction; + private final Color borderColor; + + private final Border shadowBorder; + + public BalloonBorder( int direction, Color borderColor ) { + super( 1 + SHADOW_TOP_SIZE, 1 + SHADOW_SIZE, 1 + SHADOW_SIZE, 1 + SHADOW_SIZE ); + + this.direction = direction; + this.borderColor = borderColor; + + switch( direction ) { + case SwingConstants.LEFT: left += ARROW_SIZE; break; + case SwingConstants.TOP: top += ARROW_SIZE; break; + case SwingConstants.RIGHT: right += ARROW_SIZE; break; + case SwingConstants.BOTTOM: bottom += ARROW_SIZE; break; + } + + shadowBorder = UIManager.getLookAndFeel() instanceof FlatLaf + ? new FlatDropShadowBorder( + UIManager.getColor( "Popup.dropShadowColor" ), + new Insets( SHADOW_SIZE2, SHADOW_SIZE2, SHADOW_SIZE2, SHADOW_SIZE2 ), + FlatUIUtils.getUIFloat( "Popup.dropShadowOpacity", 0.5f ) ) + : null; + } + + @Override + public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) { + Graphics2D g2 = (Graphics2D) g.create(); + try { + FlatUIUtils.setRenderingHints( g2 ); + g2.translate( x, y ); + + // shadow coordinates + int sx = 0; + int sy = 0; + int sw = width; + int sh = height; + int arrowSize = UIScale.scale( ARROW_SIZE ); + switch( direction ) { + case SwingConstants.LEFT: sx += arrowSize; sw -= arrowSize; break; + case SwingConstants.TOP: sy += arrowSize; sh -= arrowSize; break; + case SwingConstants.RIGHT: sw -= arrowSize; break; + case SwingConstants.BOTTOM: sh -= arrowSize; break; + } + + // paint shadow + if( shadowBorder != null ) + shadowBorder.paintBorder( c, g2, sx, sy, sw, sh ); + + // create balloon shape + int bx = UIScale.scale( SHADOW_SIZE ); + int by = UIScale.scale( SHADOW_TOP_SIZE ); + int bw = width - UIScale.scale( SHADOW_SIZE + SHADOW_SIZE ); + int bh = height - UIScale.scale( SHADOW_TOP_SIZE + SHADOW_SIZE ); + g2.translate( bx, by ); + Shape shape = createBalloonShape( bw, bh ); + + // fill balloon background + g2.setColor( c.getBackground() ); + g2.fill( shape ); + + // paint balloon border + g2.setColor( borderColor ); + g2.setStroke( new BasicStroke( UIScale.scale( 1f ) ) ); + g2.draw( shape ); + } finally { + g2.dispose(); + } + } + + private Shape createBalloonShape( int width, int height ) { + int arc = UIScale.scale( ARC ); + int xy = UIScale.scale( ARROW_XY ); + int awh = UIScale.scale( ARROW_SIZE ); + + Shape rect; + Shape arrow; + switch( direction ) { + case SwingConstants.LEFT: + rect = new RoundRectangle2D.Float( awh, 0, width - 1 - awh, height - 1, arc, arc ); + arrow = FlatUIUtils.createPath( awh,xy, 0,xy+awh, awh,xy+awh+awh ); + break; + + case SwingConstants.TOP: + rect = new RoundRectangle2D.Float( 0, awh, width - 1, height - 1 - awh, arc, arc ); + arrow = FlatUIUtils.createPath( xy,awh, xy+awh,0, xy+awh+awh,awh ); + break; + + case SwingConstants.RIGHT: + rect = new RoundRectangle2D.Float( 0, 0, width - 1 - awh, height - 1, arc, arc ); + int x = width - 1 - awh; + arrow = FlatUIUtils.createPath( x,xy, x+awh,xy+awh, x,xy+awh+awh ); + break; + + case SwingConstants.BOTTOM: + rect = new RoundRectangle2D.Float( 0, 0, width - 1, height - 1 - awh, arc, arc ); + int y = height - 1 - awh; + arrow = FlatUIUtils.createPath( xy,y, xy+awh,y+awh, xy+awh+awh,y ); + break; + + default: + throw new RuntimeException(); + } + + Area area = new Area( rect ); + area.add( new Area( arrow ) ); + return area; + } + } }