Why is my javafx code centering all my shapes? - javafx

I'm trying to teach myself JavaFX and tried to create a simple smiley face image. But for some reason all my shapes wind up centered instead of at the x&y coordinates that I constructed them with. I can't figure out why. Can anyone help me figure it out?
Here's my code:
import javafx.*;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.stage.Stage;
public class smiley extends Application {
public static void main(String[] args) {
launch(args);
}
public void start(Stage primaryStage) throws Exception {
Circle head = new Circle(250, 250, 150);
head.setFill(Color.YELLOW);
Circle eyeL = new Circle(200, 175, 25);
eyeL.setFill(Color.BLACK);
Circle eyeR = new Circle(300, 175, 25);
eyeR.setFill(Color.BLACK);
double[] points = { 250.0, 200.0, 250.0, 275.0, 290.0, 275.0 };
Polygon nose = new Polygon(points);
Arc smile = new Arc(275.0, 300.0, 75.0, 50.0, 180.0, 180.0);
smile.setStroke(Color.RED);
smile.setFill(Color.YELLOW);
StackPane smiley = new StackPane();
smiley.getChildren().add(head);
smiley.getChildren().add(eyeL);
smiley.getChildren().add(eyeR);
smiley.getChildren().add(nose);
smiley.getChildren().add(smile);
Scene scene = new Scene(smiley, 500, 500);
primaryStage.setTitle("Smiley");
primaryStage.setScene(scene);
primaryStage.show();
}
}
And this is what I wind up with

StackPane lays out its children on top of each other. Just think of a stack of pancakes. The last one goes on top and the first one is at the bottom. If you set a border/padding, then the children will by place in the center within those insets. Additionally, StackPane will resize all the children to fit the content area, but if that's not possible, it will then place it in the center. I don't think that this is your intent here, so try using the Pane class, which StackPane inherits from.
Pane pane = new Pane();

Related

JavaFX AnchorPane maxHeight ignored with clipped Circle

I have an AnchorPane which contains a clipped circle. I set a maximum height to the anchorpane, so that if the circle's y position is high, the circle won't be displayed. The problem is that when the circle goes to the lower part of the anchorpane, it increases it's height. This should not be happening.
This happens even before the clipped element reaches the lower part of the anchorpane. Once the "invisible" part of the circle reaches the lower part, it starts increasing it's height.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Init extends Application {
private AnchorPane canvasContainer;
private AnchorPane mainPane;
#Override
public void start(Stage stage) throws Exception {
canvasContainer = new AnchorPane();
mainPane = new AnchorPane();
Scene scene = new Scene(mainPane, 800, 700);
stage.setScene(scene);
canvasContainer.setPrefWidth(600.0d);
canvasContainer.setPrefHeight(500.0d);
//IGNORED
canvasContainer.setMaxHeight(canvasContainer.getPrefHeight());
canvasContainer.setLayoutX(14.0d);
canvasContainer.setLayoutY(14.0d);
canvasContainer.setStyle("-fx-border-color: black; -fx-border-width: 1 1 1 1;");
RadialGradient gradient = new RadialGradient(0, 0, 0.5, 0.5, 1, true, CycleMethod.NO_CYCLE, new Stop[] {
new Stop(0, Color.ORANGE),
new Stop(0.2, Color.YELLOW),
new Stop(0.5, Color.TRANSPARENT)
});
//I AM MODIFYING THIS VALUE
int y = 500;
Circle circleGradient = new Circle(200, y, 50);
circleGradient.setFill(gradient);
Rectangle rect = new Rectangle(200 - 50, y - 50, 1000, 50/2);
circleGradient.setClip(rect);
canvasContainer.getChildren().addAll(circleGradient);
mainPane.getChildren().add(canvasContainer);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I had the same issue when I was trying to make a circle grow so it would fill a rectangle which was a small area of my Scene. The filling animation worked great but the maxHeight and maxWidth of your canvasContainer are ignored. In my case that ended up with the area growing and destroying everything nearby.
Solution : add the circle to the mainPane and not canvasContainer

JavaFX | changing Line anchor?

I tried drawing a line and changing its end coordinate when dragging.
The problem is, it changes both of the lines' ends, with respect to the middle, supposedly the anchor.
Is there a way to move the anchor to the start of the line?
My code is:
Line path = new Line(30,30, 70 , 75);
path.setStrokeWidth(5);
Circle point = new Circle(3);
point.setCenterX(path.getEndX());
point.setCenterY(path.getEndY());
point.setFill(Paint.valueOf("red"));
point.setOnMouseDragged(e ->{
point.setCenterX(e.getX());
point.setCenterY(e.getY());
path.setEndX(point.getCenterX());
path.setEndY(point.getCenterY());
});
Group shapes = new Group();
shapes.getChildren().addAll(path, point);
BorderPane root = new BorderPane(shapes);
Scene scene = new Scene(root,600,400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
the result:
The ideal is that the pivot point will be at the start of the line, and not at the middle.
What you're seeing is not the line growing or shrinking in both directions; instead, what's happening is, as the line changes length, the BorderPane repositions it in order to keep it centered. The same thing is happening with the "rotation". When you move an end in such a way as to change the angle of the line, the bounding box changes in a way that, when the BorderPane repositions the line, causes the other end to move in the opposite direction.
More specifically, the BorderPane is repositioning the Group—effectively the same thing since Group doesn't position its children. One fix for this is to make the Group unmanaged. This will stop the BorderPane from repositioning the Group as its bounds change. Note, however, that this will also stop the Group from contributing to the size and layout calculations of the BorderPane.
Here's an example:
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class Main extends Application {
private static void installDragHandlers(Circle circle) {
circle.setOnMousePressed(event -> {
Point2D offset = new Point2D(
event.getX() - circle.getCenterX(),
event.getY() - circle.getCenterY()
);
circle.setUserData(offset);
event.consume();
});
circle.setOnMouseDragged(event -> {
Point2D offset = (Point2D) circle.getUserData();
circle.setCenterX(event.getX() - offset.getX());
circle.setCenterY(event.getY() - offset.getY());
event.consume();
});
circle.setOnMouseReleased(event -> {
circle.setUserData(null);
event.consume();
});
}
#Override
public void start(Stage primaryStage) {
Line line = new Line(200, 200, 400, 200);
line.setStrokeWidth(2);
Circle start = new Circle(5, Color.GREEN);
start.centerXProperty().bindBidirectional(line.startXProperty());
start.centerYProperty().bindBidirectional(line.startYProperty());
installDragHandlers(start);
Circle end = new Circle(5, Color.RED);
end.centerXProperty().bindBidirectional(line.endXProperty());
end.centerYProperty().bindBidirectional(line.endYProperty());
installDragHandlers(end);
Group group = new Group(line, start, end);
group.setManaged(false);
primaryStage.setScene(new Scene(new BorderPane(group), 600, 400));
primaryStage.setTitle("SO-55196882");
primaryStage.show();
}
}
The Line is initialized with hard coded start and end points so that it is initially centered in the scene (whose initial dimensions are also hard coded).
Put shapes in group.
public void start(final Stage primaryStage) throws AWTException {
final Line path = new Line(30, 30, 70, 75);
path.setStrokeWidth(5);
final Circle point = new Circle(3);
point.setCenterX(path.getEndX());
point.setCenterY(path.getEndY());
point.setFill(Paint.valueOf("red"));
point.setOnMouseDragged(e -> {
point.setCenterX(e.getX());
point.setCenterY(e.getY());
path.setEndX(point.getCenterX());
path.setEndY(point.getCenterY());
});
final Group root = new Group(path, point);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}

JavaFX HBox layout binding as Circle

I'm having a problem positioning JavaFX's HBox in a similar manner to Circle.
If using a circle shape it is possible to manually position it such that it is bound to a different node. This is what I've done until now, by having a Pane as the point of reference:
Pane node; //can be dragged around/resized
//...
Circle terminal = new Circle(10);
terminal.setStroke(Color.GREEN);
terminal.setFill(Color.GREEN);
terminal.centerXProperty().bind( node.layoutXProperty() );
terminal.centerYProperty().bind( node.layoutYProperty() );
The pane (node) functions as a graph node and can be dragged around and resized. The circle functions as a port/terminal for edge connections in the graph. Seeing that the node should have more than one the idea is to put the circles into an HBox that is attached/bound to the pane like the circle has until now. This makes it so that manual layout calculations are unnecessary when adding or removing ports, resizing the node, etc. So the code then used was:
Pane node; //can be dragged around/resized
//...
HBox terminalContainer = new HBox();
terminalContainer.layoutXProperty().bind( node.layoutXProperty() );
terminalContainer.layoutYProperty().bind( node.layoutYProperty() );
//... adding circles into HBox as scenegraph children
The only difference is swapping out the HBox for the Circle and using the layoutXProperty() as there is no centerXProperty(). But of course this fails, and the ports appear glued on to the top part of the containing frame, acting strangely. Is there a fix for this? I tried changing the parenting Pane to an anchorPane, this allowed to manually anchor down the HBox in the correct place, but caused issues with the resizing/dragging code.
Minimal example:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Main2 extends Application {
private AnchorPane component;
#Override
public void start(Stage primaryStage) {
component = new AnchorPane();
Scene scene = new Scene(component, 1024, 768);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
//This works, but is hard to maintain
Cell c1 = new Cell();
Cell c2 = new Cell();
Port p1 = new Port(c1);
Port p2 = new Port(c2);
component.getChildren().addAll(c1, c2, p1, p2);
c1.relocate(150, 150);
c2.relocate(550, 550);
//This does not work, even if unbinding circles, but is simpler
HBox pc1 = new HBox();
HBox pc2 = new HBox();
pc1.layoutXProperty().bind( c1.layoutXProperty() );
pc1.layoutYProperty().bind( c1.layoutYProperty() );
pc2.layoutXProperty().bind( c2.layoutXProperty() );
pc2.layoutYProperty().bind( c2.layoutYProperty() );
Port p3 = new Port(c1);
Port p4 = new Port(c2);
pc1.getChildren().add(p3);
pc2.getChildren().add(p4);
component.getChildren().addAll(pc1, pc2);
}
class Cell extends Pane {
public Cell() {
Rectangle view = new Rectangle(50,50);
view.setStroke(Color.DODGERBLUE);
view.setFill(Color.DODGERBLUE);
getChildren().add(view);
}
}
class Port extends Pane {
public Port(Cell owner) {
Circle view = new Circle(10);
view.setStroke(Color.GREEN);
view.setFill(Color.GREEN);
view.centerXProperty().bind( owner.layoutXProperty() );
view.centerYProperty().bind( owner.layoutYProperty() );
getChildren().add(view);
}
}
public static void main(String[] args) {
launch(args);
}
}
Got it to work, was a typo in the code binding the layoutXProperty twice instead of the layoutYProperty facepalm

JavaFX ScrollPane and Scaling of the Content

I would like to show a photo as an ImageView in a ScrollPane with an ZoomIn and ZoomOut Function. But if I reduce by means of scale the imageview, an undesirable empty edge is created in the ScrollPane. How can you make sure that the ScrollPane is always the size of the scaled ImageView?
See the following example. For simplicity, I replaced the ImageView with a rectangle.
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class ScrollPaneDemo extends Application {
double scale;
Pane contPane = new Pane();
#Override
public void start(Stage primaryStage) {
BorderPane pane = new BorderPane();
ScrollPane sp = new ScrollPane();
sp.setContent(contPane);
sp.setVvalue(0.5);
sp.setHvalue(0.5);
Rectangle rec = new Rectangle(2820, 1240,Color.RED);
scale = 0.2;
contPane.setScaleX(scale);
contPane.setScaleY(scale);
contPane.getChildren().add(rec);
Button but1 = new Button("+");
but1.setOnAction((ActionEvent event) -> {
scale*=2;
contPane.setScaleX(scale);
contPane.setScaleY(scale);
});
Button but2 = new Button("-");
but2.setOnAction((ActionEvent event) -> {
scale/=2;
contPane.setScaleX(scale);
contPane.setScaleY(scale);
});
HBox buttons = new HBox(but1, but2);
pane.setTop(buttons);
pane.setCenter(sp);
Scene scene = new Scene(pane, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
contPane scaled by using transform don't change its layoutBounds automatically. If you want not to make empty space in contPane, you'd better wrap the node in Group.
See this post. Layout using the transformed bounds
sp.setContent(new Group(contPane));
In addition, if you don't want to make empty space in ScrollPane, limit minimum scale to rate which width or height of the content fits viewport's one.
Button but1 = new Button("+");
but1.setOnAction((ActionEvent event) -> {
updateScale(scale * 2.0d);
});
Button but2 = new Button("-");
but2.setOnAction((ActionEvent event) -> {
updateScale(scale / 2.0d);
});
HBox buttons = new HBox(but1, but2);
pane.setTop(buttons);
pane.setCenter(sp);
Scene scene = new Scene(pane, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
updateScale(0.2d);
private void updateScale(double newScale) {
scale = Math.max(newScale, Math.max(sp.getViewportBounds().getWidth() / rec.getWidth(), sp.getViewportBounds().getHeight() / rec.getHeight()));
contPane.setScaleX(scale);
contPane.setScaleY(scale);
}
Consider a case of the image is smaller than ScrollPane's viewport. Because for showing no empty space, this code will stretch contents when it doesn't have enough size.
In a case of huge images, TravisF's comment helps you.

JavaFX: How can I best place a Label centered in a Shape?

Let's say I already have a Shape on the screen. For example:
Circle circle = new Circle(x, y, radius);
circle.setFill(Color.YELLOW);
root.getChildren().add(circle);
I would like to create a Label "over" that Circle such that the Label is centered in the Circle, the font size is maximized to fit inside the Circle, etc.
I can see how this could be accomplished via binding, but that seems needlessly complicated if the position/size of these things will never change during runtime.
Thank you in advance for your help! I'm very new to JavaFX and not all that experienced at programming in the first place, so I apologize if I should've been able to find this out via my research.
Use a StackPane to automatically center the text on top of the shape.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.*;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.text.*;
import javafx.stage.Stage;
// java 8 code.
public class Circular extends Application {
public static void main(String[] args) throws Exception {
launch(args);
}
#Override
public void start(final Stage stage) throws Exception {
Text text = createText("Xyzzy");
Circle circle = encircle(text);
StackPane layout = new StackPane();
layout.getChildren().addAll(
circle,
text
);
layout.setPadding(new Insets(20));
stage.setScene(new Scene(layout));
stage.show();
}
private Text createText(String string) {
Text text = new Text(string);
text.setBoundsType(TextBoundsType.VISUAL);
text.setStyle(
"-fx-font-family: \"Times New Roman\";" +
"-fx-font-style: italic;" +
"-fx-font-size: 48px;"
);
return text;
}
private Circle encircle(Text text) {
Circle circle = new Circle();
circle.setFill(Color.ORCHID);
final double PADDING = 10;
circle.setRadius(getWidth(text) / 2 + PADDING);
return circle;
}
private double getWidth(Text text) {
new Scene(new Group(text));
text.applyCss();
return text.getLayoutBounds().getWidth();
}
}
Related
how to put a text into a circle object to display it from circle's center?
The answer to the related question discusses different bounds types for text (such as Visual bounds), in case you need that.
StackPane stackPane = new StackPane();
Circle circle = new Circle();
Label label = new Label("Hi");
circle.setFill(Color.GOLD);
circle.setStroke(Color.GRAY);
circle.radiusProperty().bind(label.widthProperty());
stackPane.getChildren().addAll(circle, label);

Resources