I\'m doing a Klondike game. The logic is all working. I\'m just having trouble with the UI in javafx.
I\'ve been trying to move/drag the cards from the \'tableau pile\'
For knowing the original card position you should use the setTranslateX (and Y) instead of setLayoutX in your mouse handler. So when the user releases the card, you can simply invoke a Transition and let the card fly back to the layout position. If the user releases the card on a valid place, you set the translate coordinates to 0 and change the layout position or use relocate.
If you want to make the cards semitransparent, you could e. g. change the opacity or apply CSS.
By the way, I wouldn't use Clipboardcontent, it seems inappropriate for your needs.
You can move multiple objects with your mouse handling code. You simple have to apply the translation to multiple objects simultaneously. When you drag a pile, you determine the cards on top of the selected card and apply the transition to all of them.
Here's a quick example to show you how it could look like.
Card.java, you'll use an ImageView.
public class Card extends Rectangle {
static Random rand = new Random();
public Card() {
setWidth(100);
setHeight(200);
Color color = createRandomColor();
setStroke(color);
setFill( color.deriveColor(1, 1, 1, 0.4));
}
public static Color createRandomColor() {
int max = 200;
Color color = Color.rgb( (int) (rand.nextDouble() * max), (int) (rand.nextDouble() * max), (int) (rand.nextDouble() * max));
return color;
}
}
Game.java, the application.
public class Game extends Application {
static List<Card> cardList = new ArrayList<>();
@Override
public void start(Stage primaryStage) {
MouseGestures mg = new MouseGestures();
Group root = new Group();
for( int i=0; i < 10; i++) {
Card card = new Card();
card.relocate( i * 20, i * 10);
mg.makeDraggable(card);
cardList.add( card);
}
root.getChildren().addAll( cardList);
Scene scene = new Scene( root, 1600, 900);
primaryStage.setScene( scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
// TODO: don't use a static method, I only added for the example
public static List<Card> getSelectedCards( Card currentCard) {
List<Card> selectedCards = new ArrayList<>();
int i = cardList.indexOf(currentCard);
for( int j=i + 1; j < cardList.size(); j++) {
selectedCards.add( cardList.get( j));
}
return selectedCards;
}
}
MouseGestures.java, the mouse handling mechanism.
public class MouseGestures {
final DragContext dragContext = new DragContext();
public void makeDraggable(final Node node) {
node.setOnMousePressed(onMousePressedEventHandler);
node.setOnMouseDragged(onMouseDraggedEventHandler);
node.setOnMouseReleased(onMouseReleasedEventHandler);
}
EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
dragContext.x = event.getSceneX();
dragContext.y = event.getSceneY();
}
};
EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
Node node = (Node) event.getSource();
double offsetX = event.getSceneX() - dragContext.x;
double offsetY = event.getSceneY() - dragContext.y;
node.setTranslateX(offsetX);
node.setTranslateY(offsetY);
// same for the other cards
List<Card> list = Game.getSelectedCards( (Card) node);
for( Card card: list) {
card.setTranslateX(offsetX);
card.setTranslateY(offsetY);
}
}
};
EventHandler<MouseEvent> onMouseReleasedEventHandler = new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
Node node = (Node) event.getSource();
moveToSource(node);
// same for the other cards
List<Card> list = Game.getSelectedCards( (Card) node);
for( Card card: list) {
moveToSource(card);
}
// if you find out that the cards are on a valid position, you need to fix it, ie invoke relocate and set the translation to 0
// fixPosition( node);
}
};
private void moveToSource( Node node) {
double sourceX = node.getLayoutX() + node.getTranslateX();
double sourceY = node.getLayoutY() + node.getTranslateY();
double targetX = node.getLayoutX();
double targetY = node.getLayoutY();
Path path = new Path();
path.getElements().add(new MoveToAbs( node, sourceX, sourceY));
path.getElements().add(new LineToAbs( node, targetX, targetY));
PathTransition pathTransition = new PathTransition();
pathTransition.setDuration(Duration.millis(1000));
pathTransition.setNode(node);
pathTransition.setPath(path);
pathTransition.setCycleCount(1);
pathTransition.setAutoReverse(true);
pathTransition.play();
}
/**
* Relocate card to current position and set translate to 0.
* @param node
*/
private void fixPosition( Node node) {
double x = node.getTranslateX();
double y = node.getTranslateY();
node.relocate(node.getLayoutX() + x, node.getLayoutY() + y);
node.setTranslateX(0);
node.setTranslateY(0);
}
class DragContext {
double x;
double y;
}
// pathtransition works with the center of the node => we need to consider that
public static class MoveToAbs extends MoveTo {
public MoveToAbs( Node node, double x, double y) {
super( x - node.getLayoutX() + node.getLayoutBounds().getWidth() / 2, y - node.getLayoutY() + node.getLayoutBounds().getHeight() / 2);
}
}
// pathtransition works with the center of the node => we need to consider that
public static class LineToAbs extends LineTo {
public LineToAbs( Node node, double x, double y) {
super( x - node.getLayoutX() + node.getLayoutBounds().getWidth() / 2, y - node.getLayoutY() + node.getLayoutBounds().getHeight() / 2);
}
}
}
When you pick a card or a multiple of cards, they'll transition back to their origin.
Here's a screenshot where I dragged the 3rd card from top.
When you check if a card is on a valid destination, you should invoke the fixPosition method. It simply relocates the card, i. e. calculates the position using the translation values, repositions the node and sets its translation to 0.
The tricky part was the PathTransition. It's working from the center of a node, not from the x/y coordinates. Here's a thread regarding that issue to which I replied in case you wish to know more about it.