JavaFX How to - Know how three bounds - layoutBounds, boundsInLocal, and boundsInParent - are computed for a node








Question

We would like to know how to know how three bounds - layoutBounds, boundsInLocal, and boundsInParent - are computed for a node.

Answer

/*from  w ww  .j  av a2  s .  c  om*/
import java.awt.image.BufferedImage;

import java.io.File;
import java.io.IOException;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javafx.animation.PathTransition;
import javafx.animation.Timeline;

import javafx.application.Application;
import javafx.application.Platform;

import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

import javafx.embed.swing.SwingFXUtils;

import javafx.event.ActionEvent;
import javafx.event.EventHandler;

import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.geometry.Rectangle2D;
import javafx.geometry.VPos;

import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.Slider;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TitledPane;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.effect.DropShadow;
import javafx.scene.effect.Effect;
import javafx.scene.effect.Glow;
import javafx.scene.effect.Reflection;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Shear;
import javafx.scene.transform.Transform;
import javafx.scene.transform.Translate;

import javafx.stage.FileChooser;
import javafx.stage.Screen;
import javafx.stage.Stage;

import javafx.util.Duration;
import javafx.util.converter.DoubleStringConverter;

import javax.imageio.ImageIO;


/**
 * Demonstrates how three bounds - layoutBounds, boundsInLocal, and boundsInParent - are
 * computed for a node. JavaFX 2.2 is required to use this class.
 *
 * @author Kishori Sharan (www.jdojo.com)
 * @version 1.0
 *
 * <b>@copyright</b> You can use, modify, copy-paste the code for any purpose, in any way you see fit. No restrictions apply.
 */
@SuppressWarnings("deprecation")
public class Main extends Application {

    final static int AXIS_LENGTH = 150;
    final static int ARROW_LENGTH = 5;

    final static int RECTANGLE_WIDTH = 50;
    final static int RECTANGLE_HEIGHT = 20;
    final static double RECTANGLE_OPACITY = 0.5;
    final static Color RECTANGLE_STROKE_COLOR = Color.PURPLE;
    final static String RECTANGLE_STROKE_COLOR_STR = "PURPLE";
    final static double RECTANGLE_STROKE_WIDTH = 0.0;
    final static Color RECTANGLE_FILL_COLOR = Color.DARKGRAY;
    final static String RECTANGLE_FILL_COLOR_STR = "DARKGRAY";

    Slider xSlider = new Slider(-100, 100, 0);
    Slider ySlider = new Slider(-100, 100, 0);
    Slider widthSlider = new Slider(10, 200, RECTANGLE_WIDTH);
    Slider heightSlider = new Slider(10, 200, RECTANGLE_HEIGHT);
    Slider opacitySlider = new Slider(0, 1.0, RECTANGLE_OPACITY);
    Slider strokeSlider = new Slider(0.0, 10.0, RECTANGLE_STROKE_WIDTH);

    ObservableList<String> colorList =
           FXCollections.observableArrayList("BLUE", "DARKBLUE", "DARKGRAY", "DARKRED", "GRAY", "GREEN", "RED", "PURPLE",
                                             "YELLOW", "BLACK");
    ChoiceBox<String> rectFillColorChoiceBox = new ChoiceBox<String>(colorList);
    ChoiceBox<String> rectStrokeColorChoiceBox = new ChoiceBox<String>(colorList);

    Slider translateXSlider = new Slider(-200, 200, 0);
    Slider translateYSlider = new Slider(-200, 200, 0);
    Slider rotateSlider = new Slider(0, 360, 0);
    Slider scaleXSlider = new Slider(0.20, 2.0, 1.0);
    Slider scaleYSlider = new Slider(0.20, 2.0, 1.0);
    Slider shearXSlider = new Slider(-1.0, 1.0, 0);
    Slider shearYSlider = new Slider(-1.0, 1.0, 0);

    Label xLabel = new Label("");
    Label yLabel = new Label("");
    Label widthLabel = new Label("");
    Label heightLabel = new Label("");
    Label opacityLabel = new Label("");
    Label strokeLabel = new Label("");

    Label translateXLabel = new Label("");
    Label translateYLabel = new Label("");
    Label rotateLabel = new Label("");
    Label scaleXLabel = new Label("");
    Label scaleYLabel = new Label("");
    Label shearXLabel = new Label("");
    Label shearYLabel = new Label("");

    Rectangle mainRect = new Rectangle(0, 0, RECTANGLE_WIDTH, RECTANGLE_HEIGHT);
    Rectangle localBoundsRect = new Rectangle();
    Rectangle parentBoundsRect = new Rectangle();
    Rectangle layoutBoundsRect = new Rectangle();

    Line localXAxis = new Line(0, 0, AXIS_LENGTH, 0);
    Line localYAxis = new Line(0, 0, 0, AXIS_LENGTH);
    Polygon localXArrow =
           new Polygon(AXIS_LENGTH, -ARROW_LENGTH, AXIS_LENGTH, ARROW_LENGTH, AXIS_LENGTH + ARROW_LENGTH, 0, AXIS_LENGTH,
                       -ARROW_LENGTH);
    Text localXAxisLabel = new Text("x-axis");

    Polygon localYArrow =
           new Polygon(0, AXIS_LENGTH + ARROW_LENGTH, -ARROW_LENGTH, AXIS_LENGTH, ARROW_LENGTH, AXIS_LENGTH, 0,
                       AXIS_LENGTH + ARROW_LENGTH);
    Text localYAxisLabel = new Text("y-axis");
    Group localXAxisGroup = new Group(localXAxis, localXArrow, localXAxisLabel);
    Group localYAxisGroup = new Group(localYAxis, localYArrow, localYAxisLabel);


    Line parentXAxis = new Line(0, 0, AXIS_LENGTH, 0);
    Line parentYAxis = new Line(0, 0, 0, AXIS_LENGTH);

    Polygon parentXArrow =
           new Polygon(AXIS_LENGTH, -ARROW_LENGTH, AXIS_LENGTH, ARROW_LENGTH, AXIS_LENGTH + ARROW_LENGTH, 0, AXIS_LENGTH,
                       -ARROW_LENGTH);
    Text parentXAxisLabel = new Text("x-axis");

    Polygon parentYArrow =
           new Polygon(0, AXIS_LENGTH + ARROW_LENGTH, -ARROW_LENGTH, AXIS_LENGTH, ARROW_LENGTH, AXIS_LENGTH, 0,
                       AXIS_LENGTH + ARROW_LENGTH);
    Text parentYAxisLabel = new Text("y-axis");
    Group parentXAxisGroup = new Group(parentXAxis, parentXArrow, parentXAxisLabel);
    Group parentYAxisGroup = new Group(parentYAxis, parentYArrow, parentYAxisLabel);

    CheckBox layoutCbx = new CheckBox("Show");
    CheckBox localCbx = new CheckBox("Show");
    CheckBox parentCbx = new CheckBox("Show");

    CheckBox layoutAnimateCbx = new CheckBox("Animate");
    CheckBox localAnimateCbx = new CheckBox("Animate");
    CheckBox parentAnimateCbx = new CheckBox("Animate");

    ToggleGroup effectGroup = new ToggleGroup();
    RadioButton noneEffect = new RadioButton("None");
    RadioButton dropshadowEffect = new RadioButton("DropShadow");
    RadioButton reflectionEffect = new RadioButton("Reflection");
    RadioButton glowEffect = new RadioButton("Glow");

    final Color LOCAL_BOUNDS_RECT_FILL_COLOR = Color.WHITE;
    final Color PARENT_BOUNDS_RECT_FILL_COLOR = Color.WHITE;
    final Color LAYOUT_BOUNDS_RECT_FILL_COLOR = Color.WHITE;

    final Color LOCAL_BOUNDS_RECT_STROKE_COLOR = Color.BLUE;
    final Color PARENT_BOUNDS_RECT_STROKE_COLOR = Color.RED;
    final Color LAYOUT_BOUNDS_RECT_STROKE_COLOR = Color.BLACK;

    final double BOUNDS_STROKE_WIDTH = 2.0;
    final double BOUNDS_PATH_CIRCLE_RADIUS = 4.0;

    // Animating the bounds bounding boxes
    final Circle LOCAL_BOUNDS_PATH_CIRCLE = new Circle(0, 0, BOUNDS_PATH_CIRCLE_RADIUS);
    final Circle PARENT_BOUNDS_PATH_CIRCLE = new Circle(0, 0, BOUNDS_PATH_CIRCLE_RADIUS);
    final Circle LAYOUT_BOUNDS_PATH_CIRCLE = new Circle(0, 0, BOUNDS_PATH_CIRCLE_RADIUS);

    final PathTransition LOCAL_BOUNDS_PATH_TRANSITION = new PathTransition();
    final PathTransition PARENT_BOUNDS_PATH_TRANSITION = new PathTransition();
    final PathTransition LAYOUT_BOUNDS_PATH_TRANSITION = new PathTransition();

    public class BoundsRecord {
        private DoubleProperty x = new SimpleDoubleProperty();
        private DoubleProperty y = new SimpleDoubleProperty();
        private DoubleProperty w = new SimpleDoubleProperty();
        private DoubleProperty h = new SimpleDoubleProperty();

        private StringProperty boundsType = new SimpleStringProperty();

        public BoundsRecord(String boundsType) {
            this.boundsType.set(boundsType);
        }

        public BoundsRecord(String boundsType, Bounds b) {
            this.boundsType.set(boundsType);
            x.set(b.getMinX());
            y.set(b.getMinY());
            w.set(b.getWidth());
            h.set(b.getHeight());
        }

        public void update(Bounds b) {
            x.set(b.getMinX());
            y.set(b.getMinY());
            w.set(b.getWidth());
            h.set(b.getHeight());
        }

        public String getBoundsType() {
            return boundsType.get();
        }

        public double getX() {
            return x.get();
        }

        public double getY() {
            return y.get();
        }


        public double getW() {
            return w.get();
        }


        public double getH() {
            return h.get();
        }

        public StringProperty boundsTypeProperty() {
            return this.boundsType;
        }

        public DoubleProperty xProperty() {
            return this.x;
        }

        public DoubleProperty yProperty() {
            return this.y;
        }

        public DoubleProperty wProperty() {
            return this.w;
        }

        public DoubleProperty hProperty() {
            return this.h;
        }
    }

    BoundsRecord layoutBoundsData = new BoundsRecord("layoutBounds");
    BoundsRecord localBoundsData = new BoundsRecord("boundsInLocal");
    BoundsRecord parentBoundsData = new BoundsRecord("boundsInParent");

    ObservableList<BoundsRecord> boundsList =
           FXCollections.observableArrayList(layoutBoundsData, localBoundsData, parentBoundsData);
    private TableView<BoundsRecord> boundsTableView = new TableView<>(boundsList);


    private Node boundsLayoutNode;

    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void init() {
        int axisLabelPos = AXIS_LENGTH + ARROW_LENGTH + 4;
        localXAxisLabel.setLayoutX(axisLabelPos);
        localXAxisLabel.setTextOrigin(VPos.CENTER);
        localYAxisLabel.setLayoutY(axisLabelPos);
        localYAxisLabel.setTextOrigin(VPos.CENTER);

        parentXAxisLabel.setLayoutX(axisLabelPos);
        parentXAxisLabel.setTextOrigin(VPos.CENTER);
        parentYAxisLabel.setLayoutY(axisLabelPos);
        parentYAxisLabel.setTextOrigin(VPos.CENTER);

        String textFont = "-fx-font-size:9;";
        localXAxisLabel.setStyle(textFont);
        localYAxisLabel.setStyle(textFont);
        parentXAxisLabel.setStyle(textFont);
        parentYAxisLabel.setStyle(textFont);

        mainRect.setFill(RECTANGLE_FILL_COLOR);
        rectFillColorChoiceBox.setValue(RECTANGLE_FILL_COLOR_STR);
        rectStrokeColorChoiceBox.setValue(RECTANGLE_STROKE_COLOR_STR);

        initializeBoundsDetails();
        initializeBoundsPathTransition();

        // Bind labels to slider values
        bindLabelsToSliders();

        // Initialize Bounds Table View
        initializeBoundsTableView();
    }

    @Override
    public void start(Stage stage) {
        BorderPane root = new BorderPane();

        Pane parentContainer = new Pane();
        parentContainer.setPrefSize(500, 500);
        parentContainer.setPickOnBounds(false);

        //Pane parent = new Pane();
        Group parent = new Group();
        boundsLayoutNode = parent;
        //parent.setPrefSize(300, 200);
        parent.setLayoutX(200);
        parent.setLayoutY(200);
        parent.setStyle("-fx-background-color:white;");
        parent.getChildren().addAll(new Group(localXAxisGroup, localYAxisGroup),
                                    new Group(parentXAxisGroup, parentYAxisGroup), new Group(parentBoundsRect, PARENT_BOUNDS_PATH_CIRCLE),
                                    new Group(localBoundsRect, LOCAL_BOUNDS_PATH_CIRCLE), new Group(layoutBoundsRect, LAYOUT_BOUNDS_PATH_CIRCLE),
                                    new Group(mainRect));

        parentContainer.getChildren().addAll(parent);

        VBox transformsControls = getTransformationControls();
        VBox resultsControls = getResultsControls();

        BorderPane nestedPane = new BorderPane();
        nestedPane.setCenter(parentContainer);
        nestedPane.setBottom(resultsControls);
        //nestedPane.setTop(printDataTextArea);
        //printDataTextArea.setPrefColumnCount(40);
        //printDataTextArea.setPrefRowCount(3);
        root.setCenter(nestedPane);

        root.setRight(transformsControls);
        //root.setBottom(resultsControls);
        //root.setCenter(parentContainer);

        // Attach event handlers
        attachEventHandlers();

        Scene scene = new Scene(root); //, 600, 400);
        stage.setScene(scene);
        stage.setTitle("Bounds of a Node");
        Rectangle2D visualBounds = Screen.getPrimary().getVisualBounds();
        stage.setX(visualBounds.getMinX());
        stage.setY(visualBounds.getMinY());
        stage.setWidth(visualBounds.getWidth());
        stage.setHeight(visualBounds.getHeight());
        stage.show();

        // Make sure everything is in sync
        relayout();
    }


    private void initializeBoundsDetails() {
        localBoundsRect.setStroke(LOCAL_BOUNDS_RECT_STROKE_COLOR);
        localBoundsRect.setFill(LOCAL_BOUNDS_RECT_FILL_COLOR);
        localBoundsRect.setStrokeWidth(BOUNDS_STROKE_WIDTH);
        localBoundsRect.setOpacity(0.5);

        parentBoundsRect.setStroke(PARENT_BOUNDS_RECT_STROKE_COLOR);
        parentBoundsRect.setFill(PARENT_BOUNDS_RECT_FILL_COLOR);
        parentBoundsRect.setStrokeWidth(BOUNDS_STROKE_WIDTH);
        parentBoundsRect.setOpacity(0.5);

        layoutBoundsRect.setStroke(LAYOUT_BOUNDS_RECT_STROKE_COLOR);
        layoutBoundsRect.setFill(LAYOUT_BOUNDS_RECT_FILL_COLOR);
        layoutBoundsRect.setStrokeWidth(BOUNDS_STROKE_WIDTH);
        layoutBoundsRect.setOpacity(0.5);

        LOCAL_BOUNDS_PATH_CIRCLE.setFill(LOCAL_BOUNDS_RECT_STROKE_COLOR);
        PARENT_BOUNDS_PATH_CIRCLE.setFill(PARENT_BOUNDS_RECT_STROKE_COLOR);
        LAYOUT_BOUNDS_PATH_CIRCLE.setFill(LAYOUT_BOUNDS_RECT_STROKE_COLOR);

        LOCAL_BOUNDS_PATH_CIRCLE.setVisible(false);
        LAYOUT_BOUNDS_PATH_CIRCLE.setVisible(false);
        PARENT_BOUNDS_PATH_CIRCLE.setVisible(false);
    }

    private VBox getResultsControls() {
        VBox vBox = new VBox();
        vBox.setSpacing(5);
        vBox.getChildren().addAll(boundsTableView);
        return vBox;
    }

    private void bindLabelsToSliders() {
        xLabel.textProperty().bind(new SimpleStringProperty("x:").concat(xSlider.valueProperty().asString("%.2f")));
        yLabel.textProperty().bind(new SimpleStringProperty("y:").concat(ySlider.valueProperty().asString("%.2f")));

        widthLabel.textProperty().bind(new SimpleStringProperty("width:").concat(widthSlider.valueProperty().asString("%.2f")));
        heightLabel.textProperty().bind(new SimpleStringProperty("height:").concat(heightSlider.valueProperty().asString("%.2f")));
        opacityLabel.textProperty().bind(new SimpleStringProperty("opacity:").concat(opacitySlider.valueProperty().asString("%.2f")));
        strokeLabel.textProperty().bind(new SimpleStringProperty("stroke:").concat(strokeSlider.valueProperty().asString("%.2f")));

        translateXLabel.textProperty().bind(new SimpleStringProperty("TranslateX:").concat(translateXSlider.valueProperty().asString("%.2f")));
        translateYLabel.textProperty().bind(new SimpleStringProperty("TranslateY:").concat(translateYSlider.valueProperty().asString("%.2f")));

        rotateLabel.textProperty().bind(new SimpleStringProperty("Rotate:").concat(rotateSlider.valueProperty().asString("%.2f")).concat(" deg"));

        scaleXLabel.textProperty().bind(new SimpleStringProperty("ScaleX:").concat(scaleXSlider.valueProperty().asString("%.2f")));
        scaleYLabel.textProperty().bind(new SimpleStringProperty("ScaleY:").concat(scaleYSlider.valueProperty().asString("%.2f")));

        shearXLabel.textProperty().bind(new SimpleStringProperty("ShearX:").concat(shearXSlider.valueProperty().asString("%.2f")));
        shearYLabel.textProperty().bind(new SimpleStringProperty("ShearY:").concat(shearYSlider.valueProperty().asString("%.2f")));
    }

    private VBox getTransformationControls() {
        xSlider.setShowTickMarks(true);
        xSlider.setShowTickLabels(true);
        xSlider.snapToTicksProperty().set(true);

        ySlider.setShowTickMarks(true);
        ySlider.setShowTickLabels(true);
        ySlider.snapToTicksProperty().set(true);

        widthSlider.setShowTickMarks(true);
        widthSlider.setShowTickLabels(true);
        widthSlider.snapToTicksProperty().set(true);

        heightSlider.setShowTickMarks(true);
        heightSlider.setShowTickLabels(true);
        heightSlider.snapToTicksProperty().set(true);

        opacitySlider.setShowTickMarks(true);
        opacitySlider.setShowTickLabels(true);
        opacitySlider.snapToTicksProperty().set(true);
        opacitySlider.setMinorTickCount(5);
        opacitySlider.setMajorTickUnit(0.20d);

        strokeSlider.setShowTickMarks(true);
        strokeSlider.setShowTickLabels(true);
        strokeSlider.snapToTicksProperty().set(true);
        strokeSlider.setMinorTickCount(5);
        strokeSlider.setMajorTickUnit(1.0d);

        translateXSlider.setShowTickMarks(true);
        translateXSlider.setShowTickLabels(true);
        translateXSlider.snapToTicksProperty().set(true);

        translateYSlider.setShowTickMarks(true);
        translateYSlider.setShowTickLabels(true);
        translateYSlider.snapToTicksProperty().set(true);

        rotateSlider.setShowTickMarks(true);
        rotateSlider.setShowTickLabels(true);
        rotateSlider.snapToTicksProperty().set(true);
        rotateSlider.setMinorTickCount(5);
        rotateSlider.setMajorTickUnit(30.0);

        scaleXSlider.setShowTickMarks(true);
        scaleXSlider.setShowTickLabels(true);
        scaleXSlider.setMajorTickUnit(0.2d);
        scaleXSlider.setLabelFormatter(new DoubleStringConverter());
        scaleXSlider.snapToTicksProperty().set(true);

        scaleYSlider.setShowTickMarks(true);
        scaleYSlider.setShowTickLabels(true);
        scaleYSlider.setMajorTickUnit(0.2d);
        scaleYSlider.setLabelFormatter(new DoubleStringConverter());
        scaleYSlider.snapToTicksProperty().set(true);

        shearXSlider.setShowTickMarks(true);
        shearXSlider.setShowTickLabels(true);
        shearXSlider.setMajorTickUnit(0.2d);
        shearXSlider.setLabelFormatter(new DoubleStringConverter());
        shearXSlider.snapToTicksProperty().set(true);

        shearYSlider.setShowTickMarks(true);
        shearYSlider.setShowTickLabels(true);
        shearYSlider.setMajorTickUnit(0.2d);
        shearYSlider.setLabelFormatter(new DoubleStringConverter());
        shearYSlider.snapToTicksProperty().set(true);

        HBox xyBox = new HBox();
        xyBox.setSpacing(5);
        xyBox.getChildren().addAll(VBoxBuilder.create().children(xLabel, xSlider).build(),
                                   VBoxBuilder.create().children(yLabel, ySlider).build());

        HBox whBox = new HBox();
        whBox.setSpacing(5);
        whBox.getChildren().addAll(VBoxBuilder.create().children(widthLabel, widthSlider).build(),
                                   VBoxBuilder.create().children(heightLabel, heightSlider).build());

        HBox colorBox = new HBox();
        colorBox.setSpacing(5);
        colorBox.getChildren().addAll(VBoxBuilder.create().children(strokeLabel, strokeSlider).build(),
                                      VBoxBuilder.create().children(new Label("Stroke Color"),
                                                                    rectStrokeColorChoiceBox).build());

        HBox opacityBox = new HBox();
        opacityBox.setSpacing(5);
        opacityBox.getChildren().addAll(VBoxBuilder.create().children(opacityLabel, opacitySlider).build(),
                                        VBoxBuilder.create().children(new Label("Fill Color"),
                                                                      rectFillColorChoiceBox).build());


        HBox translateBox = new HBox();
        translateBox.setSpacing(5);
        translateBox.getChildren().addAll(VBoxBuilder.create().children(translateXLabel, translateXSlider).build(),
                                          VBoxBuilder.create().children(translateYLabel, translateYSlider).build());

        HBox rotateBox = new HBox();
        rotateBox.setSpacing(5);
        rotateBox.getChildren().addAll(VBoxBuilder.create().children(rotateLabel, rotateSlider).build());

        HBox scaleBox = new HBox();
        scaleBox.setSpacing(5);
        scaleBox.getChildren().addAll(VBoxBuilder.create().children(scaleXLabel, scaleXSlider).build(),
                                      VBoxBuilder.create().children(scaleYLabel, scaleYSlider).build());

        HBox shearBox = new HBox();
        shearBox.setSpacing(5);
        shearBox.getChildren().addAll(VBoxBuilder.create().children(shearXLabel, shearXSlider).build(),
                                      VBoxBuilder.create().children(shearYLabel, shearYSlider).build());

        VBox rectangleBox = new VBox();
        rectangleBox.getChildren().addAll(xyBox, whBox, colorBox, opacityBox);
        TitledPane rectangleProps = new TitledPane("Rectangle", rectangleBox);

        VBox transformBox = new VBox();
        transformBox.getChildren().addAll(translateBox, rotateBox, scaleBox, shearBox);
        TitledPane transformsProps = new TitledPane("Tranformations", transformBox);

        TitledPane showBoundsControls = getShowBoundsControls();
        TitledPane effectPane = getEffectTitledPane();

        Button resetAllButton = new Button("Reset All");
        resetAllButton.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent actionEvent) {
                resetAll();
            }
        });

        Button saveButton = new Button("Save Layout as Image");
        saveButton.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent actionEvent) {
                saveLayoutAsImage();
            }
        });

        Button exitButton = new Button("Exit");
        exitButton.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent actionEvent) {
                Platform.exit();
            }
        });

        /*
        Button printButton = new Button("Print");
        printButton.setOnAction(new EventHandler<ActionEvent>() {
                @Override
                public void handle(ActionEvent actionEvent) {
                    String str = getDesc("layoutBounds", mainRect.getLayoutBounds()) +
                                 getDesc("\nboundsInLocal", mainRect.getBoundsInLocal()) +
                                 getDesc("\nboundsInParent", mainRect.getBoundsInParent());
                    
                    //printDataTextArea.setText(str);
                }
                
                private String getDesc(String type, Bounds b) {
                    String str = type + "[minX=" + b.getMinX() +
                                 ", minY=" + b.getMinY() +
                                 ", width=" + b.getWidth() +
                                 ", height=" + b.getHeight() + "]";
                    return str;
                }
            });
        */

        HBox buttonBox = new HBox();
        buttonBox.setSpacing(10);
        buttonBox.getChildren().addAll(resetAllButton, saveButton, exitButton);

        VBox vBox = new VBox();
        vBox.getChildren().addAll(buttonBox, showBoundsControls, rectangleProps, effectPane, transformsProps);
        return vBox;
    }

    private void saveLayoutAsImage() {
        FileChooser fc = new FileChooser();
        fc.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("Portable Network Graphics(*.png)",
                                                                        Collections.singletonList("*.png")));
        File output = fc.showSaveDialog(boundsLayoutNode.getScene().getWindow());
        if (output == null) {
            return;
        }
        else {
            String fn = output.getName();
            if (!fn.toLowerCase().endsWith(".png")) {
                output = new File(output.getParent(), fn + ".png");
            }
        }

        try {
            WritableImage wImg = boundsLayoutNode.snapshot(null, null);
            //WritableImage wImg = mainRect.snapshot(null, null);
            BufferedImage bImg = SwingFXUtils.fromFXImage(wImg, null);
            ImageIO.write(bImg, "png", output);
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

    @SuppressWarnings("unchecked")
    private void initializeBoundsTableView() {
        boundsTableView.setPrefSize(200, 100);
        boundsTableView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
        boundsTableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);

        TableColumn<BoundsRecord, String> boundsTypeCol = new TableColumn<>("Bounds Type");
        boundsTypeCol.setCellValueFactory(new PropertyValueFactory<>("boundsType"));

        TableColumn<BoundsRecord, Double> xCol = new TableColumn<>("MinX");
        xCol.setCellValueFactory(new PropertyValueFactory<>("x"));

        TableColumn<BoundsRecord, Double> yCol = new TableColumn<>("MinY");
        yCol.setCellValueFactory(new PropertyValueFactory<>("y"));

        TableColumn<BoundsRecord, Double> wCol = new TableColumn<>("Width");
        wCol.setCellValueFactory(new PropertyValueFactory<>("w"));

        TableColumn<BoundsRecord, Double> hCol = new TableColumn<>("Height");
        hCol.setCellValueFactory(new PropertyValueFactory<>("h"));

        boundsTableView.getColumns().setAll(boundsTypeCol, xCol, yCol, wCol, hCol);
    }

    private void drawAllAxes() {

        // If local and parent coordinate spaces are the same,
        // we will draw axes only once
        // Transform a non-zero (x, y) point from local to parent.
        // If we get the same point back, it means two coordinate spaces are the same
        double x = 100; // any value will do
        double y = 100; // any value will do

        Point2D p = mainRect.localToParent(x, y);

        if (p.getY() == y) {
            // We do not draw parent x-axis
            parentXArrow.setVisible(false);
            parentXAxisLabel.setVisible(false);

            localXAxisLabel.setText("x-axis (Parent/Untransformed Local)");
            drawLocalAxis();
        }
        else {
            this.parentXArrow.setVisible(true);
            this.parentXAxisLabel.setVisible(true);

            localXAxisLabel.setText("x-axis (Transformed Local)");
            parentXAxisLabel.setText("x-axis (Parent/Untransformed Local)");
            drawLocalAxis();
            drawParentAxis();
        }

        if (p.getX() == x) {
            // We do not draw parent y-axis
            parentYArrow.setVisible(false);
            parentYAxisLabel.setVisible(false);

            localYAxisLabel.setText("y-axis (Parent/Untransformed Local)");
            drawLocalAxis();
        }
        else {
            parentYArrow.setVisible(true);
            parentYAxisLabel.setVisible(true);

            localYAxisLabel.setText("y-axis (Transformed Local)");
            parentYAxisLabel.setText("y-axis (Parent/Untransformed Local)");
            drawLocalAxis();
            drawParentAxis();
        }
    }

    private void drawLocalAxis() {
        localYAxisLabel.setLayoutX(-1.0 * localYAxisLabel.layoutBoundsProperty().get().getWidth() / 2.0);

        localXAxisGroup.getTransforms().clear();
        localYAxisGroup.getTransforms().clear();

        List<Transform> t = getTrsnaforms(true, true);
        localXAxisGroup.getTransforms().addAll(t);
        localYAxisGroup.getTransforms().addAll(t);
    }

    private void drawParentAxis() {
        parentYAxisLabel.setLayoutX(-1.0 * parentYAxisLabel.layoutBoundsProperty().get().getWidth() / 2.0);
    }

    private void attachEventHandlers() {
        ChangeListener<Number> cl = new ChangeListener<Number>() {
            public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
                relayout();
            }
        };

        ChangeListener<String> clStr = new ChangeListener<String>() {
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                relayout();
            }
        };

        xSlider.valueProperty().addListener(cl);
        ySlider.valueProperty().addListener(cl);
        widthSlider.valueProperty().addListener(cl);
        heightSlider.valueProperty().addListener(cl);

        strokeSlider.valueProperty().addListener(cl);
        rectStrokeColorChoiceBox.valueProperty().addListener(clStr);

        opacitySlider.valueProperty().addListener(cl);
        rectFillColorChoiceBox.valueProperty().addListener(clStr);


        translateXSlider.valueProperty().addListener(cl);
        translateYSlider.valueProperty().addListener(cl);
        rotateSlider.valueProperty().addListener(cl);
        scaleXSlider.valueProperty().addListener(cl);
        scaleYSlider.valueProperty().addListener(cl);
        shearXSlider.valueProperty().addListener(cl);
        shearYSlider.valueProperty().addListener(cl);
    }

    private void relayout() {
        mainRect.getTransforms().clear();

        mainRect.setWidth(widthSlider.getValue());
        mainRect.setHeight(heightSlider.getValue());
        mainRect.setX(xSlider.getValue());
        mainRect.setY(ySlider.getValue());

        mainRect.getTransforms().addAll(getTrsnaforms(true, true));

        mainRect.setEffect(this.getEffect());

        mainRect.setStrokeWidth(strokeSlider.getValue());
        mainRect.setStroke(RECTANGLE_STROKE_COLOR);
        mainRect.setStroke(Color.web(rectStrokeColorChoiceBox.getValue()));

        mainRect.setOpacity(opacitySlider.getValue());
        mainRect.setFill(Color.web(rectFillColorChoiceBox.getValue()));

        drawAllAxes();
        displayResults();
        showBounds();
        restartBoundsTransitions();
    }

    private void restartBoundsTransitions() {
        LAYOUT_BOUNDS_PATH_TRANSITION.stop();
        LOCAL_BOUNDS_PATH_TRANSITION.stop();
        PARENT_BOUNDS_PATH_TRANSITION.stop();
        this.animate();
    }

    private List<Transform> getTrsnaforms(boolean includeScale, boolean includeShear) {
        double tx = translateXSlider.getValue();
        double ty = translateYSlider.getValue();
        double scaleX = scaleXSlider.getValue();
        double scaleY = scaleYSlider.getValue();
        double shearX = shearXSlider.getValue();
        double shearY = shearYSlider.getValue();
        double rotation = rotateSlider.getValue();

        List<Transform> list = new ArrayList<Transform>();

        list.add(new Translate(tx, ty));
        list.add(new Rotate(rotation));

        if (includeScale) {
            list.add(new Scale(scaleX, scaleY));
        }

        if (includeShear) {
            list.add(new Shear(shearX, shearY));
        }

        return list;
    }

    private void showBounds() {
        if (layoutCbx.isSelected()) {
            Bounds b = mainRect.getLayoutBounds();
            layoutBoundsRect.setX(b.getMinX());
            layoutBoundsRect.setY(b.getMinY());
            layoutBoundsRect.setWidth(b.getWidth());
            layoutBoundsRect.setHeight(b.getHeight());

            layoutBoundsRect.setVisible(true);
        }
        else {
            layoutBoundsRect.setVisible(false);
        }

        if (localCbx.isSelected()) {
            Bounds b1 = mainRect.getBoundsInLocal();

            Bounds b = b1; // mainRect.localToParent(b1);
            localBoundsRect.setX(b.getMinX());
            localBoundsRect.setY(b.getMinY());
            localBoundsRect.setWidth(b.getWidth());
            localBoundsRect.setHeight(b.getHeight());
            localBoundsRect.setVisible(true);
        }
        else {
            localBoundsRect.setVisible(false);
        }

        if (parentCbx.isSelected()) {
            Bounds b = mainRect.getBoundsInParent();
            parentBoundsRect.setX(b.getMinX());
            parentBoundsRect.setY(b.getMinY());
            parentBoundsRect.setWidth(b.getWidth());
            parentBoundsRect.setHeight(b.getHeight());
            parentBoundsRect.setVisible(true);
        }
        else {
            parentBoundsRect.setVisible(false);
        }
    }

    private TitledPane getShowBoundsControls() {
        ChangeListener<Boolean> cl = new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observableValue, Boolean oldValue,
                                Boolean newValue) {
                relayout();
            }
        };

        ChangeListener<Boolean> cl2 = new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observableValue, Boolean oldValue,
                                Boolean newValue) {
                animate();
            }
        };

        layoutCbx.selectedProperty().addListener(cl);
        localCbx.selectedProperty().addListener(cl);
        parentCbx.selectedProperty().addListener(cl);
        effectGroup.selectedToggleProperty().addListener(new ChangeListener<Toggle>() {
            public void changed(ObservableValue<? extends Toggle> ov, Toggle old_toggle, Toggle new_toggle) {
                relayout();
            }
        });


        layoutAnimateCbx.selectedProperty().addListener(cl2);
        localAnimateCbx.selectedProperty().addListener(cl2);
        parentAnimateCbx.selectedProperty().addListener(cl2);

        double w = 20.0;
        double h = 10.0;

        Rectangle rLayout = new Rectangle(w, h);
        rLayout.setFill(LAYOUT_BOUNDS_RECT_FILL_COLOR);
        rLayout.setStrokeWidth(BOUNDS_STROKE_WIDTH);
        rLayout.setStroke(LAYOUT_BOUNDS_RECT_STROKE_COLOR);

        Rectangle rLocal = new Rectangle(w, h);
        rLocal.setFill(LOCAL_BOUNDS_RECT_FILL_COLOR);
        rLocal.setStrokeWidth(BOUNDS_STROKE_WIDTH);
        rLocal.setStroke(LOCAL_BOUNDS_RECT_STROKE_COLOR);

        Rectangle rParent = new Rectangle(w, h);
        rParent.setFill(PARENT_BOUNDS_RECT_FILL_COLOR);
        rParent.setStrokeWidth(BOUNDS_STROKE_WIDTH);
        rParent.setStroke(PARENT_BOUNDS_RECT_STROKE_COLOR);

        GridPane gp = new GridPane();
        gp.addRow(1, rLayout, new Text("Layout Bounds:"), layoutCbx, layoutAnimateCbx);
        gp.addRow(2, rLocal, new Text("Local Bounds:"), localCbx, localAnimateCbx);
        gp.addRow(3, rParent, new Text("Parent Bounds:"), parentCbx, parentAnimateCbx);

        TitledPane titledPane = new TitledPane("Show Bounds", gp);

        return titledPane;
    }

    private TitledPane getEffectTitledPane() {
        noneEffect.setToggleGroup(effectGroup);
        noneEffect.setSelected(true);

        dropshadowEffect.setToggleGroup(effectGroup);
        reflectionEffect.setToggleGroup(effectGroup);
        glowEffect.setToggleGroup(effectGroup);

        HBox hBox = new HBox();
        hBox.setPadding(new Insets(5, 5, 5, 5));
        hBox.getChildren().addAll(noneEffect, dropshadowEffect, reflectionEffect, glowEffect);

        TitledPane effectsPane = new TitledPane("Effect", hBox);

        return effectsPane;
    }

    private void animate() {
        if (layoutAnimateCbx.isSelected()) {
            LAYOUT_BOUNDS_PATH_CIRCLE.setVisible(true);
            playLayoutBoundsPathTransition();

        }
        else {
            LAYOUT_BOUNDS_PATH_CIRCLE.setVisible(false);
            LAYOUT_BOUNDS_PATH_TRANSITION.stop();
        }

        if (localAnimateCbx.isSelected()) {
            LOCAL_BOUNDS_PATH_CIRCLE.setVisible(true);
            playLocalBoundsPathTransition();

        }
        else {
            LOCAL_BOUNDS_PATH_CIRCLE.setVisible(false);
            LOCAL_BOUNDS_PATH_TRANSITION.stop();
        }

        if (parentAnimateCbx.isSelected()) {
            PARENT_BOUNDS_PATH_CIRCLE.setVisible(true);
            playParentBoundsPathTransition();

        }
        else {
            PARENT_BOUNDS_PATH_CIRCLE.setVisible(false);
            PARENT_BOUNDS_PATH_TRANSITION.stop();
        }
    }

    private void initializeBoundsPathTransition() {
        LOCAL_BOUNDS_PATH_TRANSITION.setDuration(Duration.seconds(2));
        LOCAL_BOUNDS_PATH_TRANSITION.setNode(LOCAL_BOUNDS_PATH_CIRCLE);
        LOCAL_BOUNDS_PATH_TRANSITION.setOrientation(PathTransition.OrientationType.NONE);
        LOCAL_BOUNDS_PATH_TRANSITION.setCycleCount(Timeline.INDEFINITE);

        PARENT_BOUNDS_PATH_TRANSITION.setDuration(Duration.seconds(2));
        PARENT_BOUNDS_PATH_TRANSITION.setNode(PARENT_BOUNDS_PATH_CIRCLE);
        PARENT_BOUNDS_PATH_TRANSITION.setOrientation(PathTransition.OrientationType.NONE);
        PARENT_BOUNDS_PATH_TRANSITION.setCycleCount(Timeline.INDEFINITE);

        LAYOUT_BOUNDS_PATH_TRANSITION.setDuration(Duration.seconds(2));
        LAYOUT_BOUNDS_PATH_TRANSITION.setNode(LAYOUT_BOUNDS_PATH_CIRCLE);
        LAYOUT_BOUNDS_PATH_TRANSITION.setOrientation(PathTransition.OrientationType.NONE);
        LAYOUT_BOUNDS_PATH_TRANSITION.setCycleCount(Timeline.INDEFINITE);
    }

    private void playLayoutBoundsPathTransition() {
        Bounds b = mainRect.getLayoutBounds();
        Path path = new Path();
        path.getElements().add(new MoveTo(b.getMinX(), b.getMinY()));
        path.getElements().add(new LineTo(b.getMaxX(), b.getMinY()));
        path.getElements().add(new LineTo(b.getMaxX(), b.getMaxY()));
        path.getElements().add(new LineTo(b.getMinX(), b.getMaxY()));
        path.getElements().add(new LineTo(b.getMinX(), b.getMinY()));

        LAYOUT_BOUNDS_PATH_TRANSITION.setPath(path);
        LAYOUT_BOUNDS_PATH_TRANSITION.play();
    }

    private void playLocalBoundsPathTransition() {
        Bounds b = mainRect.getBoundsInLocal();
        Path path = new Path();
        path.getElements().add(new MoveTo(b.getMinX(), b.getMinY()));
        //path.getElements().add(new LineTo(b.getMinX(), b.getMinY()));
        path.getElements().add(new LineTo(b.getMaxX(), b.getMinY()));
        path.getElements().add(new LineTo(b.getMaxX(), b.getMaxY()));
        path.getElements().add(new LineTo(b.getMinX(), b.getMaxY()));
        path.getElements().add(new LineTo(b.getMinX(), b.getMinY()));

        LOCAL_BOUNDS_PATH_TRANSITION.setPath(path);
        LOCAL_BOUNDS_PATH_TRANSITION.play();
    }

    private void playParentBoundsPathTransition() {
        Bounds b = mainRect.getBoundsInParent();

        Path path = new Path();
        path.getElements().add(new MoveTo(b.getMinX(), b.getMinY()));
        path.getElements().add(new LineTo(b.getMaxX(), b.getMinY()));
        path.getElements().add(new LineTo(b.getMaxX(), b.getMaxY()));
        path.getElements().add(new LineTo(b.getMinX(), b.getMaxY()));
        path.getElements().add(new LineTo(b.getMinX(), b.getMinY()));

        PARENT_BOUNDS_PATH_TRANSITION.setPath(path);
        PARENT_BOUNDS_PATH_TRANSITION.play();
    }

    private void resetAll() {
        xSlider.setValue(0.0);
        ySlider.setValue(0.0);
        widthSlider.setValue(RECTANGLE_WIDTH);
        heightSlider.setValue(RECTANGLE_HEIGHT);
        translateXSlider.setValue(0);
        translateYSlider.setValue(0);
        rotateSlider.setValue(0);
        scaleXSlider.setValue(1.0);
        scaleYSlider.setValue(1.0);
        shearXSlider.setValue(0);
        shearYSlider.setValue(0);

        strokeSlider.setValue(RECTANGLE_STROKE_WIDTH);
        rectStrokeColorChoiceBox.setValue(RECTANGLE_STROKE_COLOR_STR);

        opacitySlider.setOpacity(RECTANGLE_OPACITY);
        rectFillColorChoiceBox.setValue(RECTANGLE_FILL_COLOR_STR);

        layoutAnimateCbx.setSelected(false);
        localAnimateCbx.setSelected(false);
        parentAnimateCbx.setSelected(false);

        layoutCbx.setSelected(false);
        localCbx.setSelected(false);
        parentCbx.setSelected(false);

        noneEffect.setSelected(true);
    }

    private Effect getEffect() {
        Effect effect = null;
        if (dropshadowEffect.isSelected()) {
            effect = new DropShadow();
        }
        else if (reflectionEffect.isSelected()) {
            effect = new Reflection();
        }
        else if (glowEffect.isSelected()) {
            effect = new Glow();
        }

        return effect;
    }

    private void displayResults() {
        Bounds layoutBounds = mainRect.getLayoutBounds();
        Bounds localBounds = mainRect.getBoundsInLocal();
        Bounds parentBounds = mainRect.getBoundsInParent();

        layoutBoundsData.update(layoutBounds);
        localBoundsData.update(localBounds);
        parentBoundsData.update(parentBounds);
    }
}