title: JavaFx 8: Moving component between parents while staying in place
body: <p>I need to move Nodes (mosty clipped ImageViews) between containers. During the "parent change" I have to keep the components visually in place, so from the users' perspective this reorganization should be transparent.</p>
  
  <p>The schema of my scene is like this:</p>
  
  <p><img src="https://i.stack.imgur.com/NMSXZ.png" alt="enter image description here"></p>
  
  <p>The Nodes I have to move are clipped, translated some effects applied to them and originally reside under the <code>Board</code> pane. The <code>contents</code> group of the <code>desk</code> is scaled, clipped and translated.  </p>
  
  <p>I would like to move them to the MoverLayer. It works fine, due to the <code>moverLayer</code> is bound to the <code>Board</code>:</p>
  
  <pre><code>    moverLayer.translateXProperty().bind(board.translateXProperty());
      moverLayer.translateYProperty().bind(board.translateYProperty());
      moverLayer.scaleXProperty().bind(board.scaleXProperty());
      moverLayer.scaleYProperty().bind(board.scaleYProperty());
      moverLayer.layoutXProperty().bind(board.layoutXProperty());
      moverLayer.layoutYProperty().bind(board.layoutYProperty());
  </code></pre>
  
  <p>so I can simply move the nodes between them:</p>
  
  <pre><code>public void start(MouseEvent me) {
      board.getContainer().getChildren().remove(node);
      desk.getMoverLayer().getChildren().add(node);
  }
  
  
  public void finish(MouseEvent me) {
      desk.getMoverLayer().getChildren().remove(node);
      board.getContainer().getChildren().add(node);
  }
  </code></pre>
  
  <p>However, when moving nodes between the <code>contents</code> of <code>tray</code> and the <code>MoverLayer</code> it starts to get complicated. I tried to play with different coordinates (local, parent, scene, screen), but somehow it is always misplaced. It seems, that when the scale is 1.0 for <code>desk.contents</code>, it works to map the coordinates of <code>translateX</code> and <code>translateY</code> to screen coordinates, switch the parent and then map back the screen coordinates to local and use as translation. But with non-identical scaling, the coordinates differs (and the node moves). I also tried to map the coordinates to the common parent (<code>desk</code>) recursively, but works neither.</p>
  
  <p>My generalized question is, what is the best practice to calculate coordinates of the same point relative to different parents?</p>
  
  <p>Here is the MCVE code. Sorry, I simply couldn't make it more simple.</p>
  
  <pre><code>package hu.vissy.puzzlefx.stackoverflow;
  
  import javafx.application.Application;
  import javafx.geometry.BoundingBox;
  import javafx.geometry.Bounds;
  import javafx.geometry.Insets;
  import javafx.geometry.Point2D;
  import javafx.scene.Group;
  import javafx.scene.Node;
  import javafx.scene.Parent;
  import javafx.scene.Scene;
  import javafx.scene.control.Button;
  import javafx.scene.input.MouseEvent;
  import javafx.scene.layout.Background;
  import javafx.scene.layout.BackgroundFill;
  import javafx.scene.layout.CornerRadii;
  import javafx.scene.layout.Pane;
  import javafx.scene.layout.Priority;
  import javafx.scene.layout.VBox;
  import javafx.scene.paint.Color;
  import javafx.scene.shape.Rectangle;
  import javafx.stage.Stage;
  
  public class Mcve extends Application {
  
      // The piece to move
      private Rectangle piece;
  
      // The components representing the above structure
      private Pane board;
      private Group moverLayer;
      private Pane trayContents;
      private Pane desk;
      private Group deskContents;
  
      // The zoom scale
      private double scale = 1;
  
      // Drag info
      private double startDragX;
      private double startDragY;
      private Point2D dragAnchor;
  
      public Mcve() {
      }
  
      public void init(Stage primaryStage) {
  
          // Added only for simulation
          Button b = new Button("Zoom");
          b.setOnAction((ah) -&gt; setScale(0.8));
  
          desk = new Pane();
          desk.setPrefSize(800, 600);
          desk.setBackground(new Background(new BackgroundFill(Color.LIGHTGREEN, CornerRadii.EMPTY, Insets.EMPTY)));
  
          deskContents = new Group();
          desk.getChildren().add(deskContents);
  
          board = new Pane();
          board.setPrefSize(700, 600);
          board.setBackground(new Background(new BackgroundFill(Color.LIGHTCORAL, CornerRadii.EMPTY, Insets.EMPTY)));
  
          // Symbolize the piece to be dragged
          piece = new Rectangle();
          piece.setTranslateX(500);
          piece.setTranslateY(50);
          piece.setWidth(50);
          piece.setHeight(50);
          piece.setFill(Color.BLACK);
          board.getChildren().add(piece);
  
          // Mover layer is always on top and is bound to the board (used to display the 
          // dragged items above all desk contents during dragging)
          moverLayer = new Group();
          moverLayer.translateXProperty().bind(board.translateXProperty());
          moverLayer.translateYProperty().bind(board.translateYProperty());
          moverLayer.scaleXProperty().bind(board.scaleXProperty());
          moverLayer.scaleYProperty().bind(board.scaleYProperty());
          moverLayer.layoutXProperty().bind(board.layoutXProperty());
          moverLayer.layoutYProperty().bind(board.layoutYProperty());
  
          board.setTranslateX(50);
          board.setTranslateY(50);
  
          Pane tray = new Pane();
          tray.setPrefSize(400, 400);
          tray.relocate(80, 80);
  
          Pane header = new Pane();
          header.setPrefHeight(30);
          header.setBackground(new Background(new BackgroundFill(Color.LIGHTSLATEGRAY, CornerRadii.EMPTY, Insets.EMPTY)));
  
          trayContents = new Pane();
          trayContents.setBackground(new Background(new BackgroundFill(Color.BEIGE, CornerRadii.EMPTY, Insets.EMPTY)));
          VBox layout = new VBox();
          layout.getChildren().addAll(header, trayContents);
          VBox.setVgrow(trayContents, Priority.ALWAYS);
          layout.setPrefSize(400, 400);
  
          tray.getChildren().add(layout);
          deskContents.getChildren().addAll(board, tray, moverLayer, b);
  
          Scene scene = new Scene(desk);
  
          // Piece is draggable
          piece.setOnMousePressed((me) -&gt; startDrag(me));
          piece.setOnMouseDragged((me) -&gt; doDrag(me));
          piece.setOnMouseReleased((me) -&gt; endDrag(me));
  
          primaryStage.setScene(scene);
      }
  
      // Changing the scale
      private void setScale(double scale) {
          this.scale = scale;
          // Reseting piece position and parent if needed
          if (piece.getParent() != board) {
              piece.setTranslateX(500);
              piece.setTranslateY(50);
              trayContents.getChildren().remove(piece);
              board.getChildren().add(piece);
          }
          deskContents.setScaleX(getScale());
          deskContents.setScaleY(getScale());
      }
  
      private double getScale() {
          return scale;
      }
  
      private void startDrag(MouseEvent me) {
          // Saving drag options
          startDragX = piece.getTranslateX();
          startDragY = piece.getTranslateY();
          dragAnchor = new Point2D(me.getSceneX(), me.getSceneY());
  
          // Putting the item into the mover layer -- works fine with all zoom scale level
          board.getChildren().remove(piece);
          moverLayer.getChildren().add(piece);
          me.consume();
      }
  
      // Doing the drag
      private void doDrag(MouseEvent me) {
          double newTranslateX = startDragX + (me.getSceneX() - dragAnchor.getX()) / getScale();
          double newTranslateY = startDragY + (me.getSceneY() - dragAnchor.getY()) / getScale();
  
          piece.setTranslateX(newTranslateX);
          piece.setTranslateY(newTranslateY);
          me.consume();
      }
  
      private void endDrag(MouseEvent me) {
          // For MCVE's sake I take that the drop is over the tray.
  
          Bounds op = piece.localToScreen(piece.getBoundsInLocal());
  
          moverLayer.getChildren().remove(piece);
          // One of my several tries: mapping the coordinates till the common parent.
          // I also tried to use localtoScreen -&gt; change parent -&gt; screenToLocal
          Bounds b = localToParentRecursive(trayContents, desk, trayContents.getBoundsInLocal());
          Bounds b2 = localToParentRecursive(board, desk, board.getBoundsInLocal());
          trayContents.getChildren().add(piece);
          piece.setTranslateX(piece.getTranslateX() + b2.getMinX() - b.getMinX() * getScale());
          piece.setTranslateY(piece.getTranslateY() + b2.getMinY() - b.getMinY() * getScale());
          me.consume();
      }
  
  
      public static Point2D localToParentRecursive(Node n, Parent parent, double x, double y) {
          // For simplicity I suppose that the n node is on the path of the parent
          Point2D p = new Point2D(x, y);
          Node cn = n;
          while (true) {
              if (cn == parent) {
                  break;
              }
              p = cn.localToParent(p);
              cn = cn.getParent();
          }
          return p;
      }
  
      public static Bounds localToParentRecursive(Node n, Parent parent, Bounds bounds) {
          Point2D p = localToParentRecursive(n, parent, bounds.getMinX(), bounds.getMinY());
          return new BoundingBox(p.getX(), p.getY(), bounds.getWidth(), bounds.getHeight());
      }
  
      @Override
      public void start(Stage primaryStage) throws Exception {
          init(primaryStage);
  
          primaryStage.show();
      }
  
      public static void main(String[] args) {
          launch(args);
      }
  
  }
  </code></pre>