JavaFX clock number transitions - java

I am attempting to create a clock in JavaFX, everything worked out well, except for the numbers which represent the time (from 1 to 12).
I have this piece of code:
Group numbers = new Group();
for(int i = 0; i < 12; i++){
//create a label.
Label label = new Label(String.valueOf(i));
//center it
label.setTranslateX(100);
label.setTranslateY(100);
label.getTransforms().add(new Rotate(i * (360 / 12)));
//rotate it.
numbers.getChildren().add(label);
}
This doesn't work, the numbers are just rotated in the center, but i want them to move outside (to the edge of the outer circle, like a normal clock.
Can someone help me?
Thank you very much.

If you combine Translate with Rotate transforms:
Group numbers = new Group();
for(int i = 0; i < 12; i++){
//create a label.
Label label = new Label(String.valueOf(i==0?12:i));
label.getTransforms().add(new Rotate(i * (360d / 12d)));
label.getTransforms().add(new Translate(100d,100d));
numbers.getChildren().add(label);
}
You will have a 'rotated' clock:
But as you can see, you are rotating your labels, not their position.
One way to approach this is finding the position of each label by using a small Circle, rotating it to its final position and then moving the label:
Group numbers = new Group();
for(int i = 0; i < 12; i++){
//create a label.
Label label = new Label(String.valueOf(i==0?12:i));
Circle c=new Circle(1);
c.getTransforms().add(new Rotate(i * (360d / 12d)));
c.getTransforms().add(new Translate(0,-100));
label.setTranslateX(c.localToParent(0,0).getX());
label.setTranslateY(c.localToParent(0,0).getY());
numbers.getChildren().addAll(c,label);
}
Note I've adjusted the translation to start right at the 12 hour position.
You will notice the labels are located down and right from their circle, so you should move them accordingly.
EDIT
To avoid the need of fixing the labels position, you can use a StackPane to wrap each pair of circles and labels:
for(int i = 0; i < 12; i++){
Label label = new Label(String.valueOf(i==0?12:i));
Circle c=new Circle(1);
c.getTransforms().add(new Rotate((i) * (360d / 12d)));
c.getTransforms().add(new Translate(0,-100d));
label.setTranslateX(c.localToParent(0,0).getX());
label.setTranslateY(c.localToParent(0,0).getY());
StackPane sp = new StackPane(c,label);
numbers.getChildren().add(sp);
}
Finally, have a look at this question, maybe it's easier just using a circular pane.

Related

How to combine images in a JFrame?

I have been working on a project that is displaying a grid 16 x 16 of images, based on user interaction this grid follows the user on a dynamically larger base (an example would be a base that is 50 x 50) than the 16 x 16 display.
However, I am using JLabel components to display these images, and every time the user interacts I have to move each of the 256 images and erase the ones that are no longer in the 16 x 16 display grid. This results in a lag that is close to a second per key press and is close to nonfunctional.
What I am looking to try to do is to chain these images together in the total width of the ground and simply move the focus to the portion that is within the 16 x 16 grid, making the process no longer have to use nested for loops for the display.
Is it possible that I could dynamically store and create these chained images for display using a label? If are there other ways to display .png files in Java that could be stored and used in a similar manner?
An example of my current methodology of having to draw every image upon every user interaction:
User user = game.user;
int floorWidth = game.floorWidth;
int floorHeight = game.floorHeight;
int pX = user.getTile().getX();
int pY = user.getTile().getY();
int minX = Math.max(pX - GameConstants.USER_DRAW_DISTANCE, 0);
int maxX = Math.min(floorWidth, pX + GameConstants.USER_DRAW_DISTANCE);
int minY = Math.max(pY - GameConstants.USER_DRAW_DISTANCE, 0);
int maxY = Math.min(floorHeight, pY + GameConstants.USER_DRAW_DISTANCE);
for (int i = minY; i < maxY; i++)
{
for (int x = minX; x < maxX; x++)
{
Tile tile = floor.getTile(x, i);
if (tile.getHasSeen())
{
JLabel cLabel = tile.imageLabel;
cLabel.setLocation(340 + x * 32, 140 + i * 32);
cLabel.setSize(64, 64);
cLabel.setVisible(true);
panel.add(cLabel, 1);
}
}
}
In principle your idea should work. So you're probably doing something else wrong.
I've made an example, where it displays a 16x16 square of JLabels out of 256x256 JLabels. When you move the mouse over the panel, it changes the layout to show a new set of 16x16 JLabels. The change is pretty snappy, definitely not a 1 second delay.
import javax.swing.*;
import java.awt.EventQueue;
import java.awt.Dimension;
import java.awt.Color;
import java.awt.event.*;
import java.util.*;
public class GridViewer{
int x0, y0;
int N = 256;
int display = 16;
int length = 32;
List<JLabel> showing = new ArrayList<>();
List<JLabel> available = new ArrayList<>();
JPanel panel = new JPanel(){
Dimension sz = new Dimension(length*display, length*display);
#Override
public Dimension getPreferredSize(){
return sz;
}
};
public void showGui(){
JFrame frame = new JFrame();
panel.setLayout(null);
panel.addMouseMotionListener( new MouseAdapter(){
Random r = new Random();
#Override
public void mouseMoved(MouseEvent evt){
int x = evt.getX();
int y = evt.getY();
//map to position on the image to the position on the grid.
x0 = x/2;
x0 = Math.min(x0, N-display);
y0 = y/2;
y0 = Math.min(y0, N-display);
updateLayout();
}
});
for(int i = 0; i<N*N; i++){
available.add(createItem(i));
}
updateLayout();
frame.setContentPane(panel);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
/**
* Creates a solid color jlabel, could be used to load an image
* as an icon.
**/
JLabel createItem(int i){
JLabel l = new JLabel("");
int r = (i/256);
int g = (0)&255;
int b = (i%256);
int c = (r << 16 ) + ( g << 8 ) + b;
l.setBackground(new Color(c));
l.setOpaque(true);
l.setSize(length, length);
return l;
}
public void updateLayout(){
for(JLabel l: showing){
panel.remove(l);
}
for(int i = 0; i<display; i++){
for(int j = 0; j<display; j++){
JLabel l = available.get((i + x0) + (j+y0)*N);
panel.add(l);
l.setLocation( i*length, j*length);
showing.add(l);
}
}
}
public static void main(String[] args){
EventQueue.invokeLater( () -> new GridViewer().showGui() );
}
}
Some variations.
Use a GridLayout
Using a layout manager has a lot of advantages. Especially when it comes to using different displays, fonts and platforms? When adding and removing elements, it could make partially showing elements tough.
Use a large JPanel with a ScrollPane
We could create a single JPanel and add all 256x256 components to it, then use a scroll pane to set the view. This would have an advantage of completely separating the layout and the view. Somebody wants a larger window, you don't have to change the layout, the view gets bigger and you just see more of the layout. For 256x256 components, it should perform well but if you have too many components you might want to reconsider it.
Use a JPanel and override paintComponent
This would involve loading your 'png' files as awt Images (probably BufferedImages) and drawing them with the graphics object. You would need to handle all of the layout and rendering. It gives you quite a bit of power over how you want to render your components.

How do I draw 49 rectangles with nested loops with javafx?

I have to use a double array to make 49 squares. What I have only given me one rectangle.
Rectangle[][] rectArray = new Rectangle[7][7];
//grid is a GridPane containing 49 rectangles.
GridPane grid = new GridPane();
//---- add 49 rectangles to the grid pane, it is recommended to use nested loops
for(int i = 0; i < rectArray.length; i++)
{
for(int j = 0; j < rectArray.length; j++)
{
rectArray[i][j] = new Rectangle(470/7,390/7);
rectArray[i][j].setStroke(Color.BLACK);
rectArray[i][j].setFill(Color.WHITE);
grid.getChildren().add(rectArray[i][j]);
}
}
Add
GridPane.setConstraints(rectArray[i][j], i, j);
right before you add the rectangle to the grid. Right now all the rectangles are put at the same position (0, 0), so they overlap and look like one.

JavaFX - get the height of a Label item in pixels (or vertically align a label using the center of its height)

I have a program that draws a chart with an axis with values.
The values need to be drawn every 'y' pixels.
As shown in image, the black lines show where those values are drawn.
A corresponding Label is drawn next to each axis value.
The problem is that with no displacement the origin is taken at the upper left corner of the label, so the text is not vertically aligned with the line.
In order to fix that I am thinking in getting the height of the label and manually offseting the y position for the label.
How can I get the height of the label in pixels?
This is the source code used to draw the lines and the labels:
public void drawAxis() {
int i = 0;
for(i=0; i<20; i++) {
long y_pos = 0;
y_pos = i*100;
Line gridTick = new Line(0, y_pos, 20, y_pos);
Label gridLabel = new Label(getStringFromValue(y_pos));
gridLabel.setTranslateX(30);
gridLabel.setTranslateY(y_pos);
rightAxisPanel.getChildren().add(gridTick);
rightAxisPanel.getChildren().add(gridLabel);
}
}
Just set make sure the label size is bigger than actually needed and the bounds are symetrical to the y coordinates of the line. Let Label deal with the alignment.
Assuming the subclass of Parent you extend does not modify the layout position, you can do
public void drawAxis() {
int i = 0;
for(i=0; i<20; i++) {
double y_pos = i*100;
Line gridTick = new Line(0, y_pos, 20, y_pos);
Label gridLabel = new Label(getStringFromValue(y_pos));
gridLabel.setLayoutX(30);
// make sure label bounds range from y_pos-25 to y_pos+25 vertically
gridLabel.setLayoutY(y_pos-25);
gridLabel.setPrefHeight(2*25);
gridLabel.setMinHeight(Region.USE_PREF_SIZE);
// set text alignment in label area
gridLabel.setAlignment(Pos.CENTER_LEFT);
rightAxisPanel.getChildren().addAll(gridTick, gridLabel);
}
}
This assumes that you do not use font sizes that require more than 50 pixels to display vertically.

javafx make a grid of buttons

I want to make a grid with a specific amount of buttons.
I know how many buttons there are need to be because I get the number of rows and columns.
I could do a loop, but I don't know how you can place buttons next to eachother and underneath.
Secondly, the buttons need a Text and an Id, text is no problem, but how do you give them an id?
And at last, and probably most difficult, it can occur that there are a lot of rows, so that a scrollbar should be available.
At the end it should look something like this:
#Override
public void start(Stage stage) {
GridPane grid = new GridPane();
grid.setPadding(new Insets(BUTTON_PADDING));
grid.setHgap(BUTTON_PADDING);
grid.setVgap(BUTTON_PADDING);
for (int r = 0; r < NUM_BUTTON_LINES; r++) {
for (int c = 0; c < BUTTONS_PER_LINE; c++) {
int number = NUM_BUTTON_LINES * r + c;
Button button = new Button(String.valueOf(number));
grid.add(button, c, r);
}
}
ScrollPane scrollPane = new ScrollPane(grid);
stage.setScene(new Scene(scrollPane));
stage.show();
}
The best solution would be:
itemNumber starts from 0 to N:
Grid.getChildren().get(itemNumber).setId("bt"+itemNumber);
Grid.getChildren().get(itemNumber).getId();

Why do I only need to loop 23 times to draw 24 rectangles?

I've just encountered a strange behavior of the for loop and can't find an explanation.
I have a 2D game with this logical element which is a field.
//Declaration of the JPanel logical size.
private int[][] grid = new int[24][12];
Number of rows is 24, right?
I am painting this grid using this:
for (int r = 0; r < grid.length; r++ )
for (int c = 0; c < grid[r].length; c++)
Then I decided not to paint the last row and did this:
r < grid.length - 1;
nothing happened. I thought maybe I need parentheses. I added them and it still didn't work.
Then I changed it to
r < grid.length - 2;
And under this condition the last row was not painted as I originally wanted.
But now I am confused because mathematically: grid.length - 2 = 22; r < 22 means r(max) == 21 which means I am supposed to have two rows not painted. What am I missing?
Test case:
class Action extends JPanel {
public static final int FRAME_SIZE_X = 370;
public static final int FRAME_SIZE_Y = 720;
private int[][] grid = new int[24][12];
public Action() { //Setting everything for JPanel
Dimension actionDimension = new Dimension(360, 690);
setPreferredSize(actionDimension);
setVisible(true);
//...
}
#Override
protected void paintComponent(Graphics pen) {
super.paintComponent(pen);
for (int r = 0; r < grid.length - 2; r++) //Line with a problem
for (int c = 0; c < grid[r].length; c++) {
// [set up pen, etc.]
// draw line
pen.drawRect(c * 30, r * 30, FRAME_SIZE_X / 12, FRAME_SIZE_Y / 24);
}
}
}
It's late, and I know nothing about JPanel in any case, but...
public static final int FRAME_SIZE_X = 370;
public static final int FRAME_SIZE_Y = 720;
...
Dimension actionDimension = new Dimension(360, 690);
Pretty sure your panel is exactly one row (30 units) shorter than your frame - so even if you're looping 24 times and rendering 24 rows, the last one's getting clipped since there's only room for 23 rows on the screen.
Expand the panel to a height of 720 (or more), and I'll bet you notice the difference between 23 and 24 rows...
Take out a piece of paper, and draw a nice large rectangle on it. This is your JPanel. For the sake of this example, we're going to turn grid length into 3.
Take out a different color pen, and draw your first rectangle. This should go from the top left corner to the top right corner, down a third of the JPanel, back across, and back to the top left corner.
Now draw your second rectangle. We'll start a third of the JPanel down, go across to the right, go down another third, back across, and back up to where we started.
Now for the third one. But you'll notice you don't actually need to draw it. Why? The two sides and the bottom are already drawn by the JPanel, and the top is the same line as the bottom of the immediately prior row.
So while yes, another rectangle is drawn by Swing, you just don't see it because every line that it draws has already been drawn by something else. Likewise, to remove the last row, you need to stop drawing the last two rows. (You'll notice on your piece of paper that you get two rows only if you draw one rectangle.)
Note that if you change pen color between drawing your JPanel and your rows, it's a lot easier to see the problem.
for (int r = 0; r < grid.length; r++ )
If grid.length is 24, that iterates 24 times. You're forgetting about the zero. Or else your observations are awry.

Categories