JavaFX pannable GridPane - java

For my current project, I need a large even grid with a transparent background that one can zoom and pan across. I initially tried to use a ScrollPane with its setPannable(true), but soon discovered that a scrollpane background will remain opague even when you call setStyle("-fx-background-color: transparent") or setStyle("-fx-background: transparent"). So with that easy option eliminated, I need to make the GridPane in question directly pannable.
I've tried several things already, including trying to bind the mouse position to the GridPane's Translate property(which quite simply didn't work), and I failed to get the more promising alternative to work properly:
GridPane gridView = new GridPane();
int x;
int y;
gridView.setOnDragEntered( (event) -> {
x = event.getX();
y = event.getY();
});
gridView.setOnDragDetected( (event) -> {
gridView.setTranslateX(X - event.getX());
gridView.setTranslateY(Y - event.getY());
});
However, this only makes the map jump up and to the left, rather than to make it slowly pan with the mouse as intended.

Managed to mostly figure it out. The one remaining problem is that the GridPane is rather jittery when it is panned, but it's at an acceptable level for now.
int x = 0;
int y = 0;
gridView.setOnMouseDragged((event) -> {
if(x != 0){ //prevent from panning before values are initialized
gridView.setTranslateX( gridView.getTranslateX() + limit(event.getX() - x, 25));
gridView.setTranslateY( gridView.getTranslateY() + limit(event.getY() - y, 25));
}
x = (int)event.getX();
y = (int)event.getY();
});
Where the Limit(double num, double limit) method limits the input num to the range [-limit, limit]. Reducing the limit decreases the jitter, but it does so at the expense of decreasing responsiveness as well.

Related

JavaFX: Polyline scaling / zooming

this is for homework.
We have to create a JavaFX application, complying to the MVP principle, that shows a static sine-wave, with sliders to control the properties of said sine-wave.
These sliders are the amplitude, frequency, phase, and zoom. They're bound, through the presenter, to properties in my model that make up the sine-wave. These then have listeners to update the model on changes.
For drawing my sine-wave, I chose a polyline and I calculate the X and Y coordinates for each point to a observable list in my model:
for (double x = -360; x < 360; x++) {
data.add(x);
data.add(Math.sin(frequency.doubleValue() * x + phase.doubleValue()) * amplitude.doubleValue());
}
Then I reach this dataset to my view through the presenter where I give each point to my polyline:
public void setPoints(ObservableList<Double> list) {
functionLine.getPoints().clear();
functionLine.getPoints().addAll(list);
double temp;
for(int i = 0;i<functionLine.getPoints().size();i++) {
//separate x from y coordinates
if (i % 2 == 0) {
temp = functionLine.getPoints().get(i);
functionLine.getPoints().set(i, temp + (display.getWidth() / 2)); // + displaywidth/2 to get it to the center of the graph
} else {
temp = functionLine.getPoints().get(i);
functionLine.getPoints().set(i, temp + ((display.getHeight() / 2))); //same as above
}
}
}
This also doesn't perform very well because of the for-loop and the interface is laggy, but that's not why I am here.
This is what is currently looks like. The polyline and graph (two lines) are located in its own pane:
Now I have tried to also add zoom to this without increasing the width of the actual line, but I can't figure out how to properly scale around the center of my graph. Obviously I have to transform the coordinates of each point, but I don't know how. I have tried several things but it doesn't achieve what I want.
Feels like something I should be able to do on my own, but I can't apparently.
Any help would be appreciated.

Pre-set position of spawned node

I'm trying to make app that spawns new draggable nodes on pretty big pane(which is child of scrollpane), but this node should be spawned in the center of the screen.
Q is: Are there any methods to pre-set X,Y coordinates of these new imageviews?
For example:
button.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
Bounds bounds = scrollPane.getBoundsInLocal();
Bounds screenBounds = scrollPane.localToScreen(bounds);
int mX = (int) screenBounds.getMinX();
int mY = (int) screenBounds.getMinY();
Rectangle2D primScreenBounds = Screen.getPrimary().getVisualBounds();
int x = (int) ((primScreenBounds.getWidth() - mX) /4);
int y = (int) ((primScreenBounds.getHeight() - mY) /4);
/*
System.out.println("X coords:" +x);
System.out.println("Y coords:" +y);
*/
pane.getChildren().addAll(new ImageView(imgvw.getImage()));
//somehow set coordinates of new ImageView
}
});
The way you'd set the position deĀ“pends on the layout you use. However assuming you use Pane, you could use
layoutX and layoutY or
translateX and translateY
ImageView iv = new ImageView(imgvw.getImage());
iv.setLayoutX(x);
iv.setLayoutY(y);
pane.getChildren().add(iv);
Other layouts, e.g. StackPane automatically set layoutX and layoutY. In this case you could set managed to false
iv.setManaged(false);
or use the appropriate parameters for the layout type.
This might depend on the parent the ImageViews are placed in, though in general you should be able to position Nodes with Region#positionInArea() (Region is superclass of Parent). Note that this is a protected method, meaning you might want to create your own parent (e.g. by extending StackPane for example).
That being said, there are plenty of Parent Nodes, each providing their own unique behavior. Try to make sure that the desired behavior cannot be achieved using any of those before creating your own implementation. (And since you dont specify any positioning behavior, its hard to make recommendations)

Possible to Drag Shapes Within a GridPane with JavaFX

Am I correct in thinking it is not possible to drag Shapes within a GridPane? Here's a link to some code that allows the user to drag shapes around the screen: Drag and Drop Shapes
I want my shapes to have the same behavior as above, but I want them in a GridPane (eventually I'd like their movement to be locked to the X or Y axis to be moved on to adjacent shapes).
I added the below code to the "start" method. It creates circles using the same method as the example code but instead adds them to a grid. Surprisingly, this removed the ability for them to be dragged around.
GridPane grid = new GridPane();
grid.setLayoutX(300);
grid.setLayoutY(100);
int n = 3;
int m = 3;
for (int r = 0; r < n; r++) {
for (int c = 0; c < m; c++) {
Circle circle = createCircle(100, 50, 30, Color.BLACK);
grid.add(circle, c, r);
}
}
root.getChildren().add(grid);
If you want to test this just add the above code to the "start" method of the example code, just above these lines:
primaryStage.setScene(scene);
primaryStage.show();
My theory is the GridPane, because it locks the circles to certain positions, doesn't allow this dragging behavior.
Any input on how I can achieve movement of the circles along the X and Y axis when dragged?
In general, layout panes such as GridPane manage the placement of their content. Changing the layout coordinates will not affect nodes that are placed in these panes. You may find it better to use a plain Pane and manage the layout yourself for functionality such as this.
If you do want to use a GridPane, transformations (such as translations, etc) are applied after layout coordinates are computed, so you can use a translation (e.g. the one built-in with the translateX and translateY properties) to manage dragging in a layout pane.
So you can do:
circle.setOnMouseDragged((t) -> {
double offsetX = t.getSceneX() - orgSceneX;
double offsetY = t.getSceneY() - orgSceneY;
// No idea why they are doing this. c is just circle
Circle c = (Circle) (t.getSource());
c.setTranslateX(c.getTranslateX() + offsetX);
c.setTranslateY(c.getTranslateY() + offsetY);
orgSceneX = t.getSceneX();
orgSceneY = t.getSceneY();
});

Offset between click detection and button graphics in libgdx

I have a libgdx application that contains a class Button. The constructor of Button takes three arguements: Filename of graphics, position, and game (the latter being used for callbacks of various sorts).
The button scales itself based on the graphics provided, thus setting its width and height based on the properties of the graphics.
The main class, Game, when a click is detected compares the coordinates of the click up against the coordinates of the button combined with its width and height.
Now, the main issue is that there is a little bit of a horizontal offset between the button and the click coordinates, so the effect is that the graphics show up a few pixels to the right of the clickable area. I cannot for the life of me figure out the source of this discrepancy, so I would greatly appreciate some fresh eyes to see where I'm going wrong here.
Button, constructor and polling-method for clickable area.
public Rectangle getClickArea() {
return new Rectangle(pos.x - (img.getWidth() / 2), pos.y + (img.getHeight() / 2), w, h);
}
public Button(String assetfile, int x, int y, Game game) {
this.game = game;
img = new Pixmap(new FileHandle(assetfile));
pos = new Vector2(x, y);
this.w = img.getWidth();
this.h = img.getHeight();
}
A relevant snippet from InputHandler. It listens for input and passes on the event. Please note that the vertical click position is subtracted from the vertical size of the screen, as vertical 0 is opposite in InputHandler:
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
tracker.click(screenX, Settings.windowSize_Y - screenY);
return false;
}
ClickTracker (referenced as tracker in the above snippet), the Class that does the actual comparison between clicks and clickables:
public void click(int x, int y) {
Vector2 clickPos = new Vector2(x, y);
for (Tickable c : world.getPaintables())
{
if (!(c instanceof Unit))
continue;
if (((Clickable)c).getClickArea().contains(clickPos)) {
System.out.println("Clicked on unit");
}
}
for (Clickable c : clickables)
{
if (c.getClickArea().contains(clickPos)) {
c.clicked(x, y);
}
}
In short: The vertical alignment works as intended, but the horizontal is slightly off. The button graphics appear maybe around 10-20 pixels to the right of the clickable area.
I'll gladly post more info or code if needed, but I believe I have the relevant parts covered.
Edit:
As Maciej Dziuban requested, here's the snipped that draws the UI elements. batch is a SpriteBatch as provided by libgdx:
for (Paintable p : ui) {
batch.draw(new Texture(p.getImg()), p.getImgPos().x, p.getImgPos().y);
}
the getImgPos() is an interface method implemented by all drawable items:
public Vector2 getImgPos() {
return new Vector2(pos.x - (getImg().getWidth() / 2), pos.y);
}
It's worth noting that half of the horizontal image size is subtracted from the X pos, as X pos refers to the bottom center.
You have inconsistency in your position transformations:
Your clickArea's corner is pos translated by [-width/2, height/2] vector.
Your drawArea's corner is pos translated by [-width/2, 0] vector
They clearly should be the same, so if you want your pos to represent bottom-center of your entity (as you've explicitly stated) you have to change your getClickArea() method to, so it matches getImgPos().
public Rectangle getClickArea() {
return new Rectangle(pos.x - (img.getWidth() / 2), pos.y, w, h);
}
Side note: as Tenfour04 noticed, you create new texture each frame and this is huge memory leak. You should make it a field initialized in constructor or even a static variable given some buttons share the texture. Don't forget to call dispose() on resources. For more powerful asset management check out this article (note it may be an overkill in small projects).

Generating an N x N grid

What is the most painless way to create an N x N grid in a JavaFX application?
The only requirements I'm looking for is that the size of the grid will always take up the same amount of space, so more squares = smaller squares. I can set colors for the squares, and I can hover over each square individually and be able to show some for each square.
I won't know 'N' until the program runs and parses through some data to figure out how many total squares I need which is when I calculate the smallest NxN grid I can use.
From what I can tell my options are:
GridPane - Using the column constraints and row constraints to generate size and possibly add properties for hovering?
TableView - A lot more options for being able to give each cell data to show when hovered over but still difficult to just add rows and columns to start with.
Rectangles - Just generate and draw each rectangle while calculating the x and y coordinates for each square. This will make it easy to do the colors and hovering but I can't see how resizing would work but I'm ok with having a specific size for my application. I'll also have to calculate the best size to make each square to fill up the grids area.
I'm not necessarily looking for someone to code a solution, but if someone has dealt with this and knows a good way I'd like to hear about it.
Don't stray away from the original ideas. Why are you looking for "painless" ways when all the methods you've given are all viable? Here's one using your rectangles. The GridMaker.SCREEN_SIZE refers to the size of the screen you must have.
public static Pane makeGrid(int n){
double width = GridMaker.SCREEN_SIZE/n;
Pane p = new Pane();
Rectangle [][] rec = new Rectangle [n][n];
for(int i=0; i<n; i++){
for(int j=0; j<n; j++){
rec[i][j] = new Rectangle();
rec[i][j].setX(i * width);
rec[i][j].setY(j * width);
rec[i][j].setWidth(width);
rec[i][j].setHeight(width);
rec[i][j].setFill(null);
rec[i][j].setStroke(Color.BLACK);
p.getChildren().add(rec[i][j]);
}
}
return p;
}
Then simply add the mouse listener to the pane if you wish to make it change color.
p.setOnMouseClicked(new EventHandler <MouseEvent> (){
#Override
public void handle(MouseEvent me){
double posX = me.getX();
double posY = me.getY();
int colX = (int)(posX / width);
int colY = (int) (posY / width);
rec[colX][colY].setFill(Color.RED);
}
});
-- Edit
1)
2) For Hover, what kind of hover effects are you looking for? You can add Hover effects onto each rectangles, if you want me to show you how, I can definitely code it for you.

Categories