import javafx.animation.FadeTransition; import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.property.SimpleDoubleProperty; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Bounds; import javafx.geometry.Orientation; import javafx.geometry.Pos; import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.ButtonBuilder; import javafx.scene.input.MouseEvent; import javafx.scene.layout.Pane; import javafx.scene.layout.PaneBuilder; import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPaneBuilder; import javafx.scene.layout.TilePane; import javafx.scene.layout.TilePaneBuilder; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.scene.shape.RectangleBuilder; import javafx.util.Duration; public class PopupMenu { private Group group; private Scene scene; private Node content; private Pane pane; private StackPane stackpane; private TilePane tile; private Rectangle outRectangle, inRectangle, borderRectangle; private Button closeBtn; private final Double MIN_HEIGHT = 60.0, MIN_WIDTH = 80.0; private Double height, width; private final SimpleDoubleProperty sdpHeightInRect = new SimpleDoubleProperty( 0); private final SimpleDoubleProperty sdpWidthInRect = new SimpleDoubleProperty( 0); private final SimpleDoubleProperty sdpHeightBorderRect = new SimpleDoubleProperty( 0); private final SimpleDoubleProperty sdpWidthBorderRect = new SimpleDoubleProperty( 0); private final SimpleDoubleProperty tileX = new SimpleDoubleProperty(0); private final SimpleDoubleProperty tileY = new SimpleDoubleProperty(0); private final SimpleDoubleProperty closeBtnX = new SimpleDoubleProperty(0); private final SimpleDoubleProperty closeBtnY = new SimpleDoubleProperty(0); private final int BORDER_SIZE = 3; private final int CONTENT_PADDING = 20; public PopupMenu(final Scene scene, final Node content) { this.content = content; this.content.setVisible(true); this.content.layoutBoundsProperty().addListener( new InvalidationListener() { @Override public void invalidated(Observable observable) { calculateWidthAndHeight(); } }); this.group = (Group) scene.getRoot(); this.scene = scene; outRectangle = RectangleBuilder.create().width(scene.getWidth() - 2) .height(scene.getHeight() - 2).opacity(0.2).fill(Color.BLACK) .onMouseReleased(new EventHandler<MouseEvent>() { @Override public void handle(MouseEvent event) { event.consume(); closeMenu(); } }).build(); inRectangle = RectangleBuilder.create().width(sdpHeightInRect.get()) .height(sdpWidthInRect.get()).fill(Color.WHITESMOKE) .arcHeight(10).arcWidth(10).opacity(0.9).build(); inRectangle.heightProperty().bindBidirectional(sdpHeightInRect); inRectangle.widthProperty().bindBidirectional(sdpWidthInRect); borderRectangle = RectangleBuilder.create() .width(sdpWidthBorderRect.get()) .height(sdpHeightBorderRect.get()).arcHeight(10).arcWidth(10) .opacity(0.9).build(); borderRectangle.heightProperty().bindBidirectional(sdpHeightBorderRect); borderRectangle.widthProperty().bindBidirectional(sdpWidthBorderRect); tile = TilePaneBuilder.create().orientation(Orientation.HORIZONTAL) .tileAlignment(Pos.CENTER).managed(false) .children(this.content).build(); tile.layoutXProperty().bindBidirectional(tileX); tile.layoutYProperty().bindBidirectional(tileY); stackpane = StackPaneBuilder.create().alignment(Pos.CENTER) .children(outRectangle, borderRectangle, inRectangle, tile) .build(); closeBtn = ButtonBuilder.create().text("[X]").cancelButton(true) .visible(true).onAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { closeMenu(); } }).build(); closeBtn.layoutXProperty().bindBidirectional(closeBtnX); closeBtn.layoutYProperty().bindBidirectional(closeBtnY); pane = PaneBuilder.create().children(stackpane, closeBtn).build(); // This is needed to fire to set everything first time around. Otherwise // the display is not centered // when the popup is shown on subsequent ocasions without the content // being altered calculateWidthAndHeight(); } public void openMenu() { group.getChildren().addAll(pane); FadeTransition fadein = new FadeTransition(Duration.millis(800), pane); fadein.setFromValue(0.0); fadein.setToValue(1.0); fadein.play(); } public void closeMenu() { FadeTransition fadeout = new FadeTransition(Duration.millis(700), pane); fadeout.setFromValue(1.0); fadeout.setToValue(0.0); fadeout.play(); fadeout.setOnFinished(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { group.getChildren().removeAll(pane); } }); } private void calculateWidthAndHeight() { Bounds boundsInLocal = this.content.getBoundsInLocal(); height = (boundsInLocal.getHeight() < MIN_HEIGHT) ? MIN_HEIGHT : boundsInLocal.getHeight(); width = (boundsInLocal.getWidth() < MIN_WIDTH) ? MIN_WIDTH : boundsInLocal.getWidth(); sdpHeightInRect.set(height + CONTENT_PADDING); sdpWidthInRect.set(width + CONTENT_PADDING); sdpHeightBorderRect.set(sdpHeightInRect.get() + BORDER_SIZE); sdpWidthBorderRect.set(sdpWidthInRect.get() + BORDER_SIZE); tileX.set(scene.getWidth() / 2 - width / 2); tileY.set(scene.getHeight() / 2 - height / 2); closeBtnX.set((scene.getWidth() / 2) - 18); closeBtnY .set((scene.getHeight() - (scene.getHeight() - borderRectangle .getHeight()) / 2) - (((borderRectangle.getHeight() - inRectangle .getHeight()) * 2)) - 5); System.err.println("H:" + height + ", W:" + width); } }