4

Basically I've got this layout: Image of the GUI layout. It is a BorderPane (Black) with a Menubar (Red), a Container with some stuff in it, in the left sidebar (Green) and a GridPane (Gray) nested inside the center (Orange)

The cells of the GridPane are filled with ImageViews (Blue) that can disappear and when refilled, they, potentially stacked, fall from the top into their place via a parallel transition. So far so good, but I would like to hide the part of the image between the GridPane and the menubar but cannot get it to work. Does anybody have an idea how to approach this? I would also not mind just hiding the whole picture while just a part of it is sticking out.

//EDIT:

Image of the actual game The main layout is created with an fxml file. The imageviews have been added to the gridpane after launch.

rolve
  • 10,083
  • 4
  • 55
  • 75
DJSchaffner
  • 562
  • 7
  • 22
  • Are you using .fxml for the User Interface ? Can you provide an actual image also showing the problem directly with the Image etc , i mean a real image with the MenuBar , GridPane etc... Thank you :) – GOXR3PLUS Jul 31 '18 at 21:56
  • 1
    I edited the post, tell me if you need more info :) The thing in question is just the small part of the stone thats currently coming in from above. – DJSchaffner Jul 31 '18 at 23:27
  • One approach would be to take advantage of layers in JavaFx - put an opaque boundary on all four sides of your grid, "above" the layer where the grid is... – moilejter Aug 01 '18 at 01:01
  • Oou now i catched it, does the GridPane has any margin or padding being set through fxml? Can i see the fxml file? Surely i can solve your problem :) Nice game by the way!!! – GOXR3PLUS Aug 01 '18 at 01:28
  • Please provide a [mcve] – kleopatra Aug 01 '18 at 07:40

2 Answers2

3

If you check the JavaDoc for the GridPane you will see :

GridPane does not clip its content by default, so it is possible that childrens' bounds may extend outside its own bounds if a child's min size prevents it from being fit within it space.

Which is also true for every child of the class Pane. So you will need to manually handle the clipping and for that there is the method setClip(). A simple way to achieve it, would be to use a Rectangle with the dimensions of your pane for the clipping. Below there is a code which does exactly that :

public void clipChildren(Region region) {
    final Rectangle clipPane = new Rectangle();
    region.setClip(clipPane);

    region.layoutBoundsProperty().addListener((ov, oldValue, newValue) -> {
        clipPane.setWidth(newValue.getWidth());
        clipPane.setHeight(newValue.getHeight());
    });
}

To be honest, I wasn't aware of this too. There is a great article you can read which explains everything in details : JavaFX Pane Clipping

Now before I give you a full example, I would like to suggest you to use a Pane instead of a GridPane. The main reason is that you can manipulate (click, drag etc) your controls better.Also, some disclaimers, I am not a game developer, so the example below its just an example.

MainApp.java

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;

public class MainApp extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        GameBoard gameBoard = new GameBoard();

        BorderPane mainPane = new BorderPane();

        Button restartButton = new Button("Restart");
        restartButton.setOnAction(e -> {
            gameBoard.restartGame();
        });

        FlowPane controlPane = new FlowPane();
        controlPane.setAlignment(Pos.CENTER);
        controlPane.getChildren().add(restartButton);

        mainPane.setCenter(gameBoard);
        mainPane.setBottom(controlPane);

        stage.setScene(new Scene(mainPane, 600, 600));
        stage.show();
    }

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

}

GameBoard.java

import java.util.ArrayList;
import java.util.Random;

import javafx.animation.ParallelTransition;
import javafx.animation.RotateTransition;
import javafx.animation.TranslateTransition;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;

public class GameBoard extends StackPane {

    private final int TILE_WIDTH = 60;
    private final int TILE_HEIGHT = 60;

    private final int WIDTH;
    private final int HEIGHT;

    private int rows;
    private int columns;

    private ArrayList<Image> tileImages = new ArrayList<Image>();

    private final Pane gamePane = new Pane();

    public GameBoard() {
        this(9, 9);
    }

    public GameBoard(int rows, int columns) {

        this.rows = rows;
        this.columns = columns;

        WIDTH = columns * TILE_WIDTH;
        HEIGHT = rows * TILE_HEIGHT;

        // set the Clipping
        clipChildren(gamePane);

        // set the background color and also fix the dimensions 
        gamePane.setStyle("-fx-background-color : #52033D");
        gamePane.setMinSize(WIDTH, HEIGHT);
        gamePane.setPrefSize(WIDTH, HEIGHT);
        gamePane.setMaxSize(WIDTH, HEIGHT);
        getChildren().add(gamePane);

        initTilesImages();
        fillTiles();
    }

    public void clipChildren(Region region) {
        final Rectangle clipPane = new Rectangle();
        region.setClip(clipPane);

        // In case we want to make a resizable pane we need to update
        // our clipPane dimensions
        region.layoutBoundsProperty().addListener((ov, oldValue, newValue) -> {
            clipPane.setWidth(newValue.getWidth());
            clipPane.setHeight(newValue.getHeight());
        });
    }

    public void restartGame() {
        gamePane.getChildren().clear();
        fillTiles();
    }

    private void initTilesImages() {
        tileImages.add(new Image(this.getClass().getResource("/resources/redTile.png").toExternalForm()));
        tileImages.add(new Image(this.getClass().getResource("/resources/greenTile.png").toExternalForm()));
        tileImages.add(new Image(this.getClass().getResource("/resources/blueTile.png").toExternalForm()));
        tileImages.add(new Image(this.getClass().getResource("/resources/purpleTile.png").toExternalForm()));
        tileImages.add(new Image(this.getClass().getResource("/resources/whiteTile.png").toExternalForm()));
        tileImages.add(new Image(this.getClass().getResource("/resources/yellowTile.png").toExternalForm()));
    }

    // Fill with random images
    private void fillTiles() {
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
                ImageView tile = createTile(j, i);
                gamePane.getChildren().add(tile);
            }
        }
    }

    // Create the ImageView which I call "tile"
    private ImageView createTile(int x, int y) {
        Random rand = new Random();

        int index = rand.nextInt(tileImages.size());
        ImageView img = new ImageView(tileImages.get(index));

        img.setFitWidth(TILE_WIDTH);
        img.setFitHeight(TILE_HEIGHT);

        img.setTranslateX(x * TILE_WIDTH);

        // set some rotation and transition

        RotateTransition rt = new RotateTransition(Duration.millis(2000));
        rt.setFromAngle(0);
        rt.setToAngle(360);



TranslateTransition tt = new TranslateTransition(Duration.millis(2000));
    tt.setFromY(TILE_HEIGHT * (y - rows));
    tt.setToY(y * TILE_HEIGHT);
    tt.play();

    ParallelTransition pt = new ParallelTransition(img, tt, rt);
    pt.play();

    return img;
}

}

Result :

enter image description here

JKostikiadis
  • 2,847
  • 2
  • 22
  • 34
  • Excellent, this worked like a charm! I just added the clipChildren function and called it when i create my gui object. Also, well explained :) – DJSchaffner Aug 01 '18 at 15:29
1

Here's a shorter version of JKostikiadis's solution that makes use of bindings:

private void clipChildren(Region region) {
    Rectangle clip = new Rectangle();
    clip.widthProperty().bind(region.widthProperty());
    clip.heightProperty().bind(region.heightProperty());
    region.setClip(clip);
}
rolve
  • 10,083
  • 4
  • 55
  • 75