I'm trying to implement a chess board with draggable pieces as seen below. However, I'm unable to keep the piece being dragged to stay in front of other nodes to below or right to it. Left and up seems to work fine.
I tried to solve this by declaring the StackPanes forming the checkered background first and all the pieces only after that, as I read Java assigns the z-index based on the order in which the Nodes are added to their Parents. This approach is reflected below. I also tried creating a Group and adding both StackPanes and ImageViews to it in order to be able to use toFront(). Resulted in only the coordinate labels being shown.
How can I achieve the functionality I'm after?
This method creates the board:
public Parent chessBoard() {
GridPane board = new GridPane();
StackPane[][] cells = new StackPane[8][8];
// Create the board first
// (For dragging pieces to work correctly, draggable pieces must be
// added after the whole board, since z-index cannot be set explicitly
// in JavaFX.
for (int row = 0; row < 10; row++) {
for (int col = 0; col < 10; col++) {
// x and y in chess coordinate system (0-indexed)
int[] invertedY = {-1,7,6,5,4,3,2,1,0,-1};
int x = col - 1;
int y = invertedY[row];
// Coordinate labels
String[] abcLabels = {"A","B","C","D","E","F","G","H"};
if (row == 9 || row == 0) {
if (col == 0 || col == 9) continue;
Label label = new Label(abcLabels[x]);
label.setTextAlignment(TextAlignment.CENTER);
board.add(label, col, row);
continue;
} else if (col == 0 || col == 9) {
Label label = new Label(Integer.toString(y + 1));
board.add(label, col, row);
continue;
}
// Cell background color
Square square = game.getBoard().getSquare(x, y);
Color color = square.getColor() == ChessColor.BLACK
? Color.PERU : Color.BLANCHEDALMOND;
StackPane cell = cells[y][x] = new StackPane();
cell.setMaxSize(60, 60);
cell.setMinSize(60, 60);
cell.setBackground(new Background(
new BackgroundFill(color, null, null)));
board.add(cell, col, row);
}
}
// Finally, add pieces to their respective cells
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 8; x++) {
Square square = game.getBoard().getSquare(x, y);
Piece occupant = square.getOccupant();
if (occupant != null) {
String path = "/resources/" + occupant + ".png";
Image image =
new Image(getClass().getResourceAsStream(path));
DraggablePieceIcon imageView =
new DraggablePieceIcon(image);
imageView.setManaged(false);
cells[y][x].getChildren().add(imageView);
}
}
}
return board;
}
This class makes the draggable icons:
public class DraggablePieceIcon extends ImageView {
private double mouseX;
private double mouseY;
public DraggablePieceIcon(Image image) {
super(image);
setOnMousePressed(event -> {
mouseX = event.getSceneX();
mouseY = event.getSceneY();
});
setOnMouseDragged(event -> {
double deltaX = event.getSceneX() - mouseX;
double deltaY = event.getSceneY() - mouseY;
relocate(getLayoutX() + deltaX, getLayoutY() + deltaY);
mouseX = event.getSceneX();
mouseY = event.getSceneY();
});
}
}
And here's what I'm seeing:
You're adding the cells row by row from left to right. Since you add the pieces to the cells, the descendants of a cell cover the contents of cells in rows above or in the same row and left to the cell and will be covered by contents of all other cells.
To fix this you could make the parent of a dragged item the topmost node in the GridPane:
public DraggablePieceIcon(Image image) {
super(image);
setOnMousePressed(event -> {
mouseX = event.getSceneX();
mouseY = event.getSceneY();
// make cell containing this piece the top-most cell
this.getParent().toFront()
});
...
}
Note that this solution will require you to implement some logic that makes the pieces children of the cells they are moved to and move the pieces to the center of those cells. Otherwise a piece could be covered by other cells, if you drag the piece inside such a cell later...
An alternative would be to make the pieces children of the GridPane itself. You allow the pieces to be dragged around independent of the cells anyways; the association between cell and piece is important for the model (i.e. in this case the implementation of the chess rules) not for the view and usually these parts are kept seperate.
Related
I am working on a school project in Processing (Java Mode). We have a picture of how the game should look like.
So the task is to create a grid out of squares. Random squares should light up in red. If a red square is clicked, it should change colors to green and stay green.
What my code looks like at the moment:
Square[][] grid;
int cols = 20;
int rows = 20;
void setup() {
size(400, 400);
grid = new Square[cols][rows];
for (int i = 0; i < cols; i++) {
for (int j = 0; j < rows; j++) {
grid[i][j] = new Square(i*20, j*20, 20, 20);
}
}
}
void draw() {
background(0);
for (int i = 0; i < cols; i++) {
for (int j = 0; j < rows; j++) {
grid[i][j].display();
if (grid[i][j].x<mouseX && mouseX < grid[i][j].x + grid[i][j].w && grid[i][j].y<mouseY && mouseY < grid[i][j].y + grid[i][j].h && mousePressed) {
color col = color(0,204,0);
grid[i][j].update(col);
}
}
}
}
Class for squares:
class Square {
float x, y;
float w, h;
color c;
Square(float tempX, float tempY, float tempW, float tempH) {
x = tempX;
y = tempY;
w = tempW;
h = tempH;
c = color(0);
}
void display() {
stroke(0);
fill(c);
rect(x, y, w, h);
}
void update(color c) {
this.c = c;
}
}
So at the moment, every square you click turns green. I am not sure how to write the code, so that random squares change color to red and shuffle every 5 seconds.
Do you have any tips on how to proceed with the code or which thinking steps to take to be able to solve this task?
First, take your task:
So the task is to create a grid out of squares. Random squares should light up in red. If a red square is clicked, it should change colors to green and stay green.
and break it down:
create a grid out of squares: nicely done already !
Random squares should light up in red
If a red square is clicked
change colors to green and stay green
How do you use random numbers in Processing ?
The simplest method is using the random() method: you can pass two values and you'll get back a random number between those values.
Let's say you want to flip a coin so there's a (roughly) 50-50 change you get heads or tails. You could so something like:
if(random(0, 100) > 50){
println("head");
}else{
println("tails");
}
Could even be random(0.0, 1.0) > 0.5 for example, the idea is the same.
You could think of throwing a dice or a number of dices, etc.
Remember these are pseudo-random and in your own time can explore other pseudo random related methods such as randomGauss() and noise().
random() may be good enough for now, part 2 done :)
You're almost done with part 3:
if (grid[i][j].x<mouseX && mouseX < grid[i][j].x + grid[i][j].w && grid[i][j].y<mouseY && mouseY < grid[i][j].y + grid[i][j].h && mousePressed) {
but you need to also check if the clicked square is red.
Would nice to have some red squares to begin with. Let's assume color(204, 0, 0) is your red, you could simply add an additional check:
if(grid[i][j].c == color(204, 0, 0)){
println("red block clicked");
grid[i][j].c = color(0, 204, 0);
}
Which roughly turns your sketch into:
Square[][] grid;
int cols = 20;
int rows = 20;
final color RED = color(204, 0, 0);
final color GREEN = color(0, 204, 0);
void setup() {
size(400, 400);
grid = new Square[cols][rows];
for (int i = 0; i < cols; i++) {
for (int j = 0; j < rows; j++) {
grid[i][j] = new Square(i*20, j*20, 20, 20);
// roughly 50 - 50 % change a grid square will be red
if (random(0, 100) > 50) {
grid[i][j].update(RED);
}
}
}
}
void draw() {
background(0);
for (int i = 0; i < cols; i++) {
for (int j = 0; j < rows; j++) {
grid[i][j].display();
if (grid[i][j].x<mouseX && mouseX < grid[i][j].x + grid[i][j].w && grid[i][j].y<mouseY && mouseY < grid[i][j].y + grid[i][j].h && mousePressed) {
// if the square is red
if (grid[i][j].c == RED) {
// change colour to GREEN
grid[i][j].update(GREEN);
}
}
}
}
}
class Square {
float x, y;
float w, h;
color c;
Square(float tempX, float tempY, float tempW, float tempH) {
x = tempX;
y = tempY;
w = tempW;
h = tempH;
c = color(0);
}
void display() {
stroke(0);
fill(c);
rect(x, y, w, h);
}
void update(color c) {
this.c = c;
}
}
In terms of shuffling colours every 5 seconds I recommend:
for every 5 seconds you could use millis()
above there is an example of shuffling done in setup() though you might want to encapsulate a nested loop like that with the random condition in a void shuffle() function for example which you could easily call every 5 seconds.
note that this approach will reset green blocks to red, you might want an else in that condition to reset blocks to black (otherwise, with time, most will turn red), etc.
Have fun!
P.S. I tend to separate state data from representation. For example I would add a variable to keep track of each square state (e.g. OFF, INTERACTIVE, ACTIVATED), update a basic finite state machine, then render colours accordingly. What you have above is a tight coupling between the colour of a Square and it's state. For the homework you've got that's ok, but in the future, for more complex projects you might want to consider data flows through your program and how you represent it.
I am new to JavaFx and I wanted to know how to draw a grid, where I want to draw points on the grid corners. Should I use a gridpane as a foundation or a linechart ? What are the best classes to use a grid and draw on it ?
I wouldn’t use a GridPane, as its children are not guaranteed to be the same size, only to have their grid cell edges aligned.
A TilePane, however, does guarantee that its cells are the same size. You can then use a Group to combine the TilePane with nodes, such as Circles, which are centered on the points between the grid cells using some basic math:
public class Grid
extends Application {
private int rows = 10;
private int columns = 10;
private int spacing = 8;
#Override
public void start(Stage stage) {
TilePane pane = new TilePane(spacing, spacing);
pane.setPrefColumns(columns);
Group group = new Group(pane);
for (int row = 1; row < rows; row++) {
for (int col = 1; col < columns; col++) {
Circle point = new Circle(2);
point.setFill(Color.BLACK);
// x = ((tilewidth + hgap) * col) - (hgap / 2)
// y = ((tileheight + vgap) * row) - (vgap / 2)
point.centerXProperty().bind(
pane.tileWidthProperty().add(pane.hgapProperty())
.multiply(col)
.subtract(pane.hgapProperty().divide(2)));
point.centerYProperty().bind(
pane.tileHeightProperty().add(pane.vgapProperty())
.multiply(row)
.subtract(pane.vgapProperty().divide(2)));
group.getChildren().add(point);
}
}
// Example grid content
for (int row = 0; row < rows; row++) {
for (int col = 0; col < columns; col++) {
Text text = new Text(String.valueOf(row * rows + col));
pane.getChildren().add(text);
}
}
stage.setScene(new Scene(group));
stage.setTitle("Grid");
stage.show();
}
}
I want to create and call a method to check if a click happened over the grid. This method should take in the position of the mouse click and should return a cell number that is the selected cell. If a cell was clicked on, you need to determine which cell was clicked in and return that cell
number. Make sure that if a cell was already selected, and the user didn’t select a new cell, the old
selected cell still stays selected. If the user clicked in a cell, and that specific cell
was already selected, it should be unselected. In this case, it should return -1.
I did this to get the cell number but cannot make sure it stays selected and not able to return -1 to unselect.
int cellSelected(int x, int y){
int selected=-2;
int left=40;
for(int num=0; num<=12*8; num++)
{
int col = num%8;
int row = num/8;
left = 40+CELL_SIZE*col;
int right = left+CELL_SIZE;
int top = 40+CELL_SIZE*row;
int bottom = top+CELL_SIZE;
if (x >= left && x < right && y >= top && y < bottom)
{
selected = num;
}
}
return selected;
}
To calculate the index of a cell, it is sufficient to divide the relative mouse position (in relation to the origin of the grid), by the size of a cell.
int cellSelected(int x, int y){
// position of the mouse relative to the grid
int px = x - 40;
int py = y - 40;
// evaluate if the mouse is in the grid, return -1 else
int cols = 8;
int rows = 12;
if (px < 0 || py < 0 || px > cols*CELL_SIZE || px > cols*CELL_SIZE) {
return -1;
}
// calculate cell index (row and column of the cell)
int col = px / CELL_SIZE;
int row = py / CELL_SIZE;
// return index of the cell
return rows*cols + col;
}
My question concerns the Color Grid teechart. I'm trying to write code so that when the user touches a box on the grid, only that box color will change. I find the using chart.getHeight() and chart.getWidth() give the whole area of the chart and not just the color grid dimensions. So right now, I'm estimating the length and width of the grid in pixels to estimate the box that the user touched. Is there any way that I can figure out the exact amount of pixels of just the color grid length and height? Additionally, I noticed a "clicked" method in the Color Grid api. Is there anything already built-in that would allow me to find which box the user touched/clicked? Thanks!
I've made a simple example that uses the seriesClicked event. However, it seems to give a correct index but the cell colored seems to be in the opposite row:
oldIndex = -1;
tChart1.getAspect().setView3D(false);
tChart1.getLegend().setVisible(false);
ColorGrid col1 = new ColorGrid(tChart1.getChart());
col1.setColorEach(true);
for (int x=0; x<10; x++)
for (int z=0; z<10; z++)
col1.add(x, 1, z, Color.random());
col1.addSeriesMouseListener(new SeriesMouseAdapter() {
#Override
public void seriesClicked(SeriesMouseEvent e) {
ColorGrid myGrid = (ColorGrid)tChart1.getSeries(0);
int valueIndex = myGrid.clicked(e.getPoint().x, e.getPoint().y);
if (valueIndex > -1) {
if (oldIndex > -1) {
myGrid.getColors().setColor(oldIndex, oldColor);
}
oldIndex = valueIndex;
oldColor = myGrid.getValueColor(valueIndex);
myGrid.getColors().setColor(valueIndex, Color.red);
tChart1.getHeader().setText(String.valueOf(valueIndex));
tChart1.getSeries(0).repaint();
}
}
});
I'll add to the defect list the need to revise the clicked function for the ColorGrid (TJ71016603)
In the meanwhile, a workaround could be to modify the given valueIndex as follows:
if (valueIndex > -1) {
valueIndex = valueIndex / myGrid.getNumZValues() * myGrid.getNumZValues() + (myGrid.getNumZValues()-1 - (valueIndex % myGrid.getNumZValues()));
if (oldIndex > -1) {
//...
}
I have a JTable that is within a JScrollPane. Rows are added to the table at runtime based on events that happen in my application. I want to have the scoll pane scroll to the bottom of the table when a new row is added to the table.
For JLists There is the [ensureIndexIsVisible][1]() that forces a particular index in the list to be visible. I'm looking for the same thing but for a JTable. It looks like I might have to manually move the scrolling view on the scroll pane but I figured there had to be an easier way.
It's very easy, JTable has scrollRectToVisible method too. If you want, you can try something like this to make scrollpane go to to the bottom if a new record is added :
jTable1.getSelectionModel().setSelectionInterval(i, i);
jTable1.scrollRectToVisible(new Rectangle(jTable1.getCellRect(i, 0, true)));
Where i is last added record.
See this example : http://www.exampledepot.com/egs/javax.swing.table/Vis.html
update: the link is now obsolete, here is the code (from http://smi-protege.stanford.edu/repos/protege/protege-core/trunk/src/edu/stanford/smi/protege/util/ComponentUtilities.java )
public static void scrollToVisible(JTable table, int rowIndex, int vColIndex) {
if (!(table.getParent() instanceof JViewport)) {
return;
}
JViewport viewport = (JViewport)table.getParent();
// This rectangle is relative to the table where the
// northwest corner of cell (0,0) is always (0,0).
Rectangle rect = table.getCellRect(rowIndex, vColIndex, true);
// The location of the viewport relative to the table
Point pt = viewport.getViewPosition();
// Translate the cell location so that it is relative
// to the view, assuming the northwest corner of the
// view is (0,0)
rect.setLocation(rect.x-pt.x, rect.y-pt.y);
table.scrollRectToVisible(rect);
// Scroll the area into view
//viewport.scrollRectToVisible(rect);
}
JList internally use scrollRectToVisible and specify the coordinates to scroll to. I think you will have to recode a similar functionality for JTable.
The first answer works well, but the selected row gets positioned at the bottom of the table. So I created this modified version:
private void scrollToVisible(int rowIndex, int vColIndex ) {
JTable table = getTablePanel().getTable();
if (!(table.getParent() instanceof JViewport)) {
return;
}
if (table.getRowCount()<1){
return;
}
JViewport viewport = (JViewport)table.getParent();
// view dimension
Dimension dim = viewport.getExtentSize();
// cell dimension
Dimension dimOne = new Dimension(0,0);
// This rectangle is relative to the table where the
// northwest corner of cell (0,0) is always (0,0).
Rectangle rect = table.getCellRect(rowIndex, vColIndex, true);
Rectangle rectOne;
if (rowIndex+1<table.getRowCount()) {
if (vColIndex+1<table.getColumnCount())
vColIndex++;
rectOne = table.getCellRect(rowIndex+1, vColIndex, true);
dimOne.width=rectOne.x-rect.x;
dimOne.height=rectOne.y-rect.y;
}
// '+ veiw dimension - cell dimension' to set first selected row on the top
rect.setLocation(rect.x+dim.width-dimOne.width, rect.y+dim.height-dimOne.height);
table.scrollRectToVisible(rect);
}
Now the selected row gets positioned at the top of the table.
It seems to me a lot easier to set the viewport position instead of scrolling the table. Following is my code.
public void scrollCellToView(int rowIndex, int vColIndex) {
if (!(this.getParent() instanceof JViewport)) {
return;
}
JViewport viewport = (JViewport) this.getParent();
Rectangle rect = this.getCellRect(rowIndex, vColIndex, true);
Rectangle viewRect = viewport.getViewRect();
int x = viewRect.x;
int y = viewRect.y;
if (rect.x >= viewRect.x && rect.x <= (viewRect.x + viewRect.width - rect.width)){
} else if (rect.x < viewRect.x){
x = rect.x;
} else if (rect.x > (viewRect.x + viewRect.width - rect.width)) {
x = rect.x - viewRect.width + rect.width;
}
if (rect.y >= viewRect.y && rect.y <= (viewRect.y + viewRect.height - rect.height)){
} else if (rect.y < viewRect.y){
y = rect.y;
} else if (rect.y > (viewRect.y + viewRect.height - rect.height)){
y = rect.y - viewRect.height + rect.height;
}
viewport.setViewPosition(new Point(x,y));
}