Мне нужно перемещать узлы (в большинстве случаев обрезанные ImageView) между контейнерами. Во время «родительского изменения» я должен визуально удерживать компоненты на месте, поэтому с точки зрения пользователей эта реорганизация должна быть прозрачной.
Схема моей сцены такая:
Узлы, которые мне нужно переместить, обрезаются, транслируются некоторые применяемые к ним эффекты и изначально находятся под панелью Board
. Группа contents
в desk
масштабируется, обрезается и переводится.
Я хочу переместить их в MoverLayer. Работает нормально, потому что moverLayer
привязан к Board
:
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());
поэтому я могу просто перемещать узлы между ними:
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);
}
Однако при перемещении узлов между contents
tray
и MoverLayer
это начинает усложняться. Я пробовал играть с разными координатами (локальные, родительские, сцена, экран), но почему-то всегда неуместно. Кажется, что когда масштаб равен 1.0 для desk.contents
, он работает, чтобы сопоставить координаты translateX
и translateY
с координатами экрана, переключить родительский элемент, а затем преобразовать координаты экрана в локальные и использовать в качестве перевода. Но при неидентичном масштабировании координаты различаются (и узел перемещается). Я также попытался рекурсивно сопоставить координаты с общим родителем (desk
), но ничего не вышло.
Мой общий вопрос: как лучше всего рассчитывать координаты одной и той же точки относительно разных родителей?
Вот код MCVE. Извините, я просто не мог сделать это проще.
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) -> 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) -> startDrag(me));
piece.setOnMouseDragged((me) -> doDrag(me));
piece.setOnMouseReleased((me) -> 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 -> change parent -> 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);
}
}
A
с родительскогоX
на родительскийY
без изменения внешнего вида (положения, размера). Я могу предположить, чтоX
иY
находятся на одном дереве (имеют общего предка) и даже что они перекрываются в том месте, где находитсяA
. - person Balage1551   schedule 11.06.2014