I can not understand how moveTo method work in javafx. here is my example
I have a GridPane that consist of 4 columns and 1 row. All the columns h has a StackPane and the last Stackpane also has an empty label.
public class PathTransitionTExample extends Application {
StackPane pane;
GridPane grid;
Label label;
Scene scene;
#Override
public void start(Stage primaryStage) {
label = new Label();
label.setPrefSize(Double.MAX_VALUE, Double.MAX_VALUE);
grid = new GridPane();
grid.getStyleClass().add("gridPane");
for (int x = 0; x < 4; x++) {
grid.add(pane = new StackPane(), x, 0);
pane.setPrefSize(50, 50);
pane.getStyleClass().add("stackPane");
}
pane = (StackPane) grid.getChildren().get(3);
pane.getChildren().add(label);
scene = new Scene(grid, 260, 50);
scene.getStylesheets().add(PathTransitionTest.class.getResource("pathCSS.css").toExternalForm());
scene.setOnMouseClicked(me -> pathTransition());
primaryStage.setTitle("Path Transition");
primaryStage.setScene(scene);
primaryStage.show();
}
//pathCSS.css
.gridPane{
-fx-background-color: red;
-fx-hGap: 20;
}
.stackPane{
-fx-background-color: orange;
}
.label {
-fx-background-color: blue;
}
I want to move the label in the grid pane from 0.3 to 0.0 however when I am trying to do that the whole transition slips.
private void pathTransition() {
//the Coordinates of the label
double oldMinX = grid.getChildren().get(3).getLayoutX();
double oldMinY = grid.getChildren().get(3).getLayoutY();
//the coordinates of the stack pane where i want the label move
double newMinX = grid.getChildren().get(1).getLayoutX();
double newMinY = grid.getChildren().get(1).getLayoutY();
Path path = new Path();
path.getElements().add(new MoveTo(oldMinX, oldMinY ));
path.getElements().add(new LineTo(newMinX,newMinY ));
PathTransition pathTransition = new PathTransition();
pathTransition.setDuration(new Duration(500));
pathTransition.setPath(path);
pathTransition.setNode(label);
pathTransition.play();
}
If I change the arguments of MoveTo and LineTo by the following I can achieve the animation I want but I cant understand why. :\
double oldMinX = grid.getChildren().get(3).getLayoutX() -185;
double oldMinY = grid.getChildren().get(3).getLayoutY()+ grid.getChildren().get(3).getBoundsInLocal().getHeight()/2 ;
double newMinX = grid.getChildren().get(1).getLayoutX()-255 ;
double newMinY = grid.getChildren().get(1).getLayoutY() + grid.getChildren().get(0).getBoundsInLocal().getHeight()/2 ;
I guess It is because transitions use different coordinate systems than scenes but I cant really find anything that explains well :( Could someone give me some hints how It is working?
Thank you so much in advance.
I realized that i shouldn't use GridPane. If i do it without using containers the transition is working fine.
Related
I try to align the vertical scroll position of two javafx.scene.control.ScrollPanes via
sp1.vvalueProperty().bindBidirectional(sp2.vvalueProperty());
The problem is that one of these ScrollPanes may have a horizontal scroll bar. So the more I scroll down the ScrollPanes, the more they get misaligned (see screenshot). How can I handle this?
It's impossible to do this with 2 ScrollPanes and contents of equal height unless you display the scrollbar in both ScrollPanes:
Consider the case where the content fits the viewport of the left ScrollPane exactly. The viewPort of the right ScrollPane can be scrolled by the ScrollBar height. Modifying the left ScrollPane is not possible.
Since the expected result seems to be some kind of scale, you could simply use a Pane with a child you apply transformY to and a clip. The formula to calculate the pixel to be placed at the top, use
top = vvalue * (contentHeight - viewportHeight)
Example
private static Label createLabel(int num, boolean mark) {
Label label = new Label(Integer.toString(num));
label.setPrefSize(50, 50);
label.setMaxSize(Double.MAX_VALUE, Region.USE_PREF_SIZE);
label.setStyle(mark ? "-fx-background-color: #FFFFFF" : "-fx-background-color: #BBBBBB;");
return label;
}
#Override
public void start(Stage primaryStage) throws Exception {
VBox scale = new VBox();
scale.setMinHeight(Region.USE_PREF_SIZE);
GridPane content = new GridPane();
for (int i = 0; i < 40; i++) {
boolean b = ((i % 2) == 0);
scale.getChildren().add(createLabel(i, !b));
for (int j = 0; j < 10; j++) {
content.add(createLabel(i * 10 + j, b), j, i);
}
}
AnchorPane scaleContainer = new AnchorPane(scale);
scaleContainer.setMinWidth(30);
scaleContainer.setMinHeight(0);
AnchorPane.setLeftAnchor(scale, 0d);
AnchorPane.setRightAnchor(scale, 0d);
Rectangle clip = new Rectangle();
scaleContainer.setClip(clip);
clip.widthProperty().bind(scaleContainer.widthProperty());
clip.heightProperty().bind(scaleContainer.heightProperty());
ScrollPane scroll = new ScrollPane(content);
scale.translateYProperty().bind(Bindings.createDoubleBinding(() -> {
double contentHeight = content.getHeight();
double viewportHeight = scroll.getViewportBounds().getHeight();
if (contentHeight <= viewportHeight) {
return 0d;
} else {
return -scroll.getVvalue() * (contentHeight - viewportHeight);
}
}, scroll.viewportBoundsProperty(), scroll.vvalueProperty(), content.heightProperty()));
HBox root = new HBox(scaleContainer, scroll);
root.setPadding(new Insets(10));
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
I'm creating circles of size 5 with something like Circle c = new Circle(x, y, 5);. Then I do c.setOnMousePressed(mousePressedEventHandler); but I've got a problem here : my circle is too small and it's easy to miss it. I would like to keep this size so is there a way to increase the hitbox of a circle without doing something like creating an invisible circle bigger and then set the listner on it ?
Increase the hit area by adding a transparent stroke to the circles.
Note: To actually use hit boxes you need to set the pickOnBounds property to true.
private static Circle createCircle(double x, double y, double radius, double hitRadius) {
Circle circle = new Circle(x, y, radius, Color.BLACK);
circle.setStrokeType(StrokeType.OUTSIDE);
circle.setStroke(Color.TRANSPARENT);
circle.setStrokeWidth((hitRadius < radius) ? 0 : (hitRadius - radius));
return circle;
}
#Override
public void start(Stage primaryStage) {
Pane root = new Pane();
Circle circle1 = createCircle(100, 100, 5, 20);
Circle circle2 = createCircle(150, 150, 5, 20);
circle1.setOnMouseClicked(evt -> System.out.println("clicked 1"));
circle2.setOnMouseClicked(evt -> System.out.println("clicked 2"));
root.getChildren().addAll(
circle1,
circle2
);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.setResizable(false);
primaryStage.show();
}
I have a VBox which contains Panes. Over the VBox, I want to show another element (currently I use a Pane). This element has to overlay multiple Panes in the VBox, so I put this element together with the VBox inside an AnchorPane. The complete structure looks like this:
HBox
VBox
Label <--- "Friday"
AnchorPane
VBox <--- the VBox with Panes
Pane
...
Pane <--- the Pane over the VBox (red in images)
The problem is that when I resize the window, the red Pane does not resize nor change its position.
Normal size:
Small size:
I want the red Pane to start at the same line (3rd) and be of the same relative size.
I tried binding the Pane's prefWidthProperty and prefHeightProperty to its parent (AnchorPane). This works for auto-resizing.
For auto-positioning, I have tried to bind the layoutX|Y properties, which didn't work, because these are set by the system and I get exception "Bound value cannot be set". So I tried to make the Pane unmanaged, which in turn broke the auto-resize, since in unmanaged nodes, changes in preferred properties have no effect. I thought of binding widthProperty and heightProperty to the parent's properties, but these are read-only.
Consider using a GridPane for functionality like this. You can add multiple nodes to the same cell(s) in the grid (the ones added last will appear on top in z-order). A GridPane allows maximum flexibility for layout. Here's an example: note that there's a lot of styling here that I just hard-coded for brevity, but in a real app you should move this to an external stylesheet (you can do the same with the max sizes, etc):
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Region;
import javafx.scene.layout.RowConstraints;
import javafx.stage.Stage;
public class CalendarExample extends Application {
#Override
public void start(Stage primaryStage) {
GridPane calendar = new GridPane();
// headers:
for (DayOfWeek dayOfWeek = DayOfWeek.MONDAY ; dayOfWeek.getValue() <= DayOfWeek.FRIDAY.getValue();
dayOfWeek = DayOfWeek.of(dayOfWeek.getValue() + 1) ) {
Label label = new Label(dayOfWeek.toString());
label.setAlignment(Pos.CENTER);
label.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
label.setStyle("-fx-background-color: black, darkgray; -fx-background-insets: 0, 0 0 1 1 ;");
calendar.add(label, dayOfWeek.getValue(), 0);
}
DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("hh:mm");
int rowCount = 0 ;
for (LocalTime time = LocalTime.of(8, 0); time.isBefore(LocalTime.of(17, 0)); time=time.plusMinutes(30)) {
rowCount++ ;
Label label = new Label(timeFormatter.format(time));
label.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
label.setAlignment(Pos.CENTER);
String color = rowCount % 2 == 0 ? "darkgray" : "lightgray" ;
label.setStyle("-fx-background-color: black, "+color+"; -fx-background-insets: 0, 0 0 1 1;");
calendar.add(label, 0, rowCount);
}
// cells:
for (int x = 1 ; x <= 5; x++) {
for (int y = 1 ; y <= rowCount; y++) {
Region region = new Region();
String color = y % 2 == 0 ? "darkgray" : "lightgray" ;
region.setStyle("-fx-background-color: black, "+color+"; -fx-background-insets: 0, 0 0 1 1 ;");
calendar.add(region, x, y);
}
}
// Column constraints:
for (int x = 0 ; x <= 5 ; x++) {
ColumnConstraints cc = new ColumnConstraints();
cc.setPercentWidth(100.0 / 6);
cc.setFillWidth(true);
calendar.getColumnConstraints().add(cc);
}
// row constraints:
for (int y = 0 ; y <= rowCount; y++) {
RowConstraints rc = new RowConstraints();
rc.setPercentHeight(100.0 / (rowCount+1));
rc.setFillHeight(true);
calendar.getRowConstraints().add(rc);
}
// Example appointment block:
DayOfWeek appointmentDay = DayOfWeek.FRIDAY ;
LocalTime startTime = LocalTime.of(10, 0);
LocalTime endTime = LocalTime.of(13, 0);
String appointmentText = "Fridays need really long coffee breaks";
Label appointment = new Label(appointmentText);
appointment.setTooltip(new Tooltip(appointmentText));
appointment.setWrapText(true);
appointment.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
appointment.setStyle("-fx-background: red; -fx-background-color: -fx-background; " );
GridPane.setMargin(appointment, new Insets(2, 5, 2, 2));
Scene scene = new Scene(calendar, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
This also behaves as needed when the window is resized:
width and height properties may be readonly, but you can assign the prefered sizes and use autosize.
Example
The following code makes the overlay start at 50% height and have a height of 1/3 of the VBox height:
#Override
public void start(Stage primaryStage) {
Region region1 = new Region();
VBox.setVgrow(region1, Priority.ALWAYS);
region1.setStyle("-fx-background-color: blue;");
Region region2 = new Region();
VBox.setVgrow(region2, Priority.ALWAYS);
region2.setStyle("-fx-background-color: lime;");
Region regionOverlay = new Region();
regionOverlay.setStyle("-fx-background-color: red;");
regionOverlay.setManaged(false);
VBox root = new VBox(region1, region2, regionOverlay);
root.layoutBoundsProperty().addListener((observable, oldValue, newValue) -> {
regionOverlay.setPrefSize(newValue.getWidth() - 20, newValue.getHeight() / 3);
regionOverlay.setLayoutX(10);
regionOverlay.setLayoutY(newValue.getHeight() / 2);
regionOverlay.autosize();
});
root.setPrefSize(400, 400);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
If all you need is a red rectangle, i'd advise you to use the Rectangle shape instead of panes. This allows you to bind its widths and heights explicitly.
I'm trying to visualize a graph in JavaFX. The nodes shall be interactive. At the moment I have a VBox and place as many HBoxes into it as I need levels in my graph. All the HBoxes are set to position their childrens centered. The childrens represent the individual nodes and are Buttons.
The VBox itself is placed in a StackPane with a Canvas as another children of the StackPane.
This StackPane is then placed into the Scene.
I want to use the Canvas to draw the joining Edges between the nodes. To get The coordinates of the nodes i use:
System.out.println(button.localToScene(button.getBoundsInLocal().getMinX(),
button.getBoundsInLocal().getMinY()));
But no matter where the button is positioned, the resulting coordinate remains:
Point2D [x = 0.0, y = 108.0]
for all the buttons. Every single Button has apparently the same coordinate though it's displayed correctly!
Please help me, im quite desperate right now...
Edit:
Here is the minimal example that works:
Main.java
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
VBox root = new VBox();
HBox buttons = new HBox();
TabPane tabs = new TabPane();
buttons.getChildren().addAll(
new Button("1"),
new Button("2"),
new Button("3"));
tabs.getTabs().addAll(
new TabBoxes("____A____"),
new TabBoxes("____B____"),
new TabBoxes("____C____")
);
root.getChildren().addAll(buttons, tabs);
primaryStage.setScene(new Scene(root, 500, 400));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
TabBoxes.java
public class TabBoxes extends Tab {
private VBox vBox = new VBox();
private ArrayList<HBox> hBoxes;
private Canvas canvas = new Canvas();
private StackPane stack = new StackPane();
private static final int V_INIT_SPACING = 60;
private static final int V_SPACING = 60;
private static final int H_SPACING = 60;
TabBoxes(String s){
super(s);
this.setContent(stack);
setClosable(false);
stack.getChildren().add(canvas);
stack.getChildren().add(vBox);
canvas.setWidth(2000);
canvas.setHeight(1080);
hBoxes = new ArrayList<>();
vBox.setSpacing(V_SPACING);
vBox.translateYProperty().set(V_INIT_SPACING);
for (int i = 0; i < 5; i++) {
hBoxes.add(new HBox() {
{
setSpacing(H_SPACING);
setAlignment(Pos.CENTER);
}
});
}
for (int i = 0; i < 5; i++) {
for (int j = 0; j <= i; j++) {
hBoxes.get(i).getChildren().add(new Button(i + "||" + j){
{
setPrefSize(100,60);
setOnAction(e-> System.out.println("My coordinates are: " + localToScene(getBoundsInLocal().getMinX(),getBoundsInLocal().getMinY())));
}
});
}
}
vBox.getChildren().addAll(hBoxes);
}
}
boundsInLocal are coordinates in the Button's own coordinate system. It's not surprising, that these values are the same.
If you want to calculate the coordinates in the Canvas coordinate system, transform the coordinates to the StackPane coordinates and then transform the coordinates to the Canvas coordinates:
Node n = button;
Point2D p = new Point2D(button.getBoundsInLocal().getMinX(), button.getBoundsInLocal().getMinY());
while (n != stackPane) {
p = n.localToParent(p);
n = n.getParent();
}
p = canvas.parentToLocal(p);
My problem is with Z-Buffer in JavaFX 3D, it does not seem to work as intended on my machine.
I'am aware of questions:
Overlapping shapes
and
...Z Order...
However I do have Z-Buffer enabled, and nodes are still being rendered in the order they are added to the scenegraph.
Maybe I'm missing some dependencies or whatsoever?
I'm posting the code, I hope someone can help me. I'm creating a transition that moves the node around another on an elliptic path.
Thank you in advance!
public class OrbitExp extends Application {
Group root = new Group();
Scene scene = new Scene(root, 800, 600, true, SceneAntialiasing.BALANCED);
PerspectiveCamera camera = new PerspectiveCamera();
#Override
public void start(Stage primaryStage) {
root.setDepthTest(DepthTest.ENABLE);
//Tried to set Depthtest explicitly. Assumed maybe it did not inherit:S
System.out.println(
"3D supported? " +
Platform.isSupported(ConditionalFeature.SCENE3D)
); // returns true
System.out.println("root z-buffer: " + root.getDepthTest());
initCamera();
Box
box1 = new Box(50,50,50),
box2 = new Box(10,10,10);
root.setTranslateX(scene.getWidth()/2);
root.setTranslateY(scene.getHeight()/2);
PhongMaterial
pmat = new PhongMaterial(Color.BLUE),
pmat2 = new PhongMaterial(Color.RED);
box1.setMaterial(pmat);
box2.setMaterial(pmat2);
scene.setFill(Color.LIGHTGREEN);
root.getChildren().addAll(box1,box2);
SequentialTransition sqt = orbit(box1, box2, 40, 40, Duration.seconds(3), 360);
sqt.play();
scene.setOnMouseClicked(click->{
Node node = (Node)(click.getPickResult().getIntersectedNode());
System.out.println("Tx: "+node.getTranslateX());
System.out.println("Ty: "+node.getTranslateY());
System.out.println("Tz: "+node.getTranslateZ());
});
// just for debugging, but coords does seem to be alright
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
private void initCamera() {
camera.setTranslateZ(-50);
camera.setTranslateY(20);
camera.setFarClip(5000);
camera.setNearClip(0);
scene.setCamera(camera);
}
SequentialTransition orbit(Node node1, Node node2,double a, double b, Duration totalDuration, int N) {
SequentialTransition sqt = new SequentialTransition();
Duration dur = new Duration(totalDuration.toMillis()*(1.0d/N));
node2.setTranslateX(a+node1.getTranslateX());
node2.setTranslateZ(node1.getTranslateZ());
for (int i = 1; i < N; i++) {
TranslateTransition tt = new TranslateTransition(dur, node2);
double
angle = i*(360.0d/N),
toX = (Math.cos(Math.toRadians(angle))*a)+node1.getTranslateX(),
toZ = (Math.sin(Math.toRadians(angle))*b)+node1.getTranslateZ();
tt.setToX(toX);
tt.setToZ(toZ);
tt.setInterpolator(Interpolator.LINEAR);
sqt.getChildren().add(tt);
System.out.println("angle = " + angle + "\nangle in rads: " + Math.toRadians(angle) + "\ntoX = " + toX + "\ntoZ = " + toZ);
}
sqt.setCycleCount(Timeline.INDEFINITE);
return sqt;
}
}
This was my first post by the way:)
If you check the code on the link you provided using rectangles, depth buffer works fine.
Changing the rectangles to use 3D boxes works as well.
The issue is how you define the rotation of one of the boxes related to the other, so instead of using a RotateTransition or a SequentialTransition of TranslateTransition like you do, I've applied a Rotate transform to the red box setting a pivot in the center of the blue one, and used an AnimationTimer to modify the angle of that rotation to create the 'orbit' effect.
You can even use transparency on the big box (since 8u60) to see the small one beneath it.
private final Group shapes = new Group();
private long lastTimerCall;
private AnimationTimer timeline;
#Override
public void start(Stage stage) throws Exception {
Scene scene = new Scene(createRotatingShapes(), 400, 300,
true, SceneAntialiasing.BALANCED);
scene.setFill(Color.LIGHTGREEN);
final PerspectiveCamera camera = new PerspectiveCamera();
camera.setRotationAxis(Rotate.X_AXIS);
camera.setRotate(10);
camera.setTranslateZ(200);
scene.setCamera(camera);
stage.setScene(scene);
stage.show();
}
private Group createRotatingShapes() {
final Box box1 = new Box(50, 50, 50);
// Transparency in box1: last node of the group
box1.setMaterial(new PhongMaterial(Color.web("#0000FF80")));
box1.setTranslateZ(50);
final Box box2 = new Box(10, 10, 10);
box2.setMaterial(new PhongMaterial(Color.RED));
box2.setTranslateZ(-50);
shapes.getChildren().addAll(box2, box1);
shapes.setTranslateX(200);
shapes.setTranslateY(150);
rotateAroundYAxis(box2);
return shapes;
}
private int count = 0;
private void rotateAroundYAxis(Node node) {
Rotate r = new Rotate(0, 0, 0, 100, Rotate.Y_AXIS);
node.getTransforms().add(r);
lastTimerCall = System.nanoTime();
timeline = new AnimationTimer() {
#Override public void handle(long now) {
if (now > lastTimerCall + 100_000_000l) {
r.setAngle((count++)%360);
}
}
};
timeline.start();
}
#Override
public void stop() {
timeline.stop();
}
In front of the box:
Behind the blue box:
EDIT
If you have a look to the Camera JavaDoc for nearClip:
Specifies the distance from the eye of the near clipping plane of
this Camera in the eye coordinate space.
Objects closer to the eye than nearClip are not drawn.
nearClip is specified as a value greater than zero. A value less
than or equal to zero is treated as a very small positive number.
(bold is mine).
So the problem with your code was this line:
camera.setNearClip(0);
Just change it to:
camera.setNearClip(0.01);
and it will work as you expected.