I am trying to get an isometric view of a grid. The grid is only part of the main scene so I created a subscene and I want add a camera to it. I will want to be able to zoom the camera in and out and pan it around preserving the viewing angle throughout all these operation.
Here is what I have without the camera:
public class MyApp extends Application {
#Override
public void start(Stage stage) throws Exception {
Button actionButton = new Button("Placeholder\t\t\t\t\t\t\t\t\t\t\t\t");
HBox hbox = new HBox(actionButton);
BorderPane mainPane = new BorderPane(new MyView(), null, null, hbox, null);
Scene scene = new Scene(mainPane);
stage.setScene(scene);
stage.show();
}
private class MyView extends Group {
MyView() {
super();
GridPane grid = new GridPane();
for (int i = 0; i < 64; i++) {
Rectangle tile = new Rectangle(30, 30, Color.GREEN);
BorderPane pane = new BorderPane(tile);
pane.setBorder(new Border(new BorderStroke(null, BorderStrokeStyle.SOLID,
null, null, null)));
grid.add(pane, i / 8, i % 8);
}
Group root = new Group();
root.getChildren().add(grid);
SubScene scene = new SubScene(root, 300, 300, true, SceneAntialiasing.BALANCED);
scene.setFill(Color.DARKCYAN); // just to see the area
getChildren().add(scene);
}
}
public static void main(String[] args) throws Exception {
launch(args);
}
}
Now I add the camera like this in the MyView constructor:
Camera camera = new PerspectiveCamera(true);
scene.setCamera(camera);
and the grid disappears.
I didn't even do any transformation yet (i would do camera.getTransforms().addAll(new Rotate(-15, Rotate.Y_AXIS));). What am i doing wrong?
also, how can I tell the subscene to take whatever space is available? I don't want to need to specify specific size because the program can run on all sorts of screens.
Your camera is positioned at the same z coordinates as the Group. However you have to make sure it's at a distance between farClip and nearClip:
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setTranslateZ(-100);
camera.setFieldOfView(120);
Furthermore for isometric views PerspectiveCamera is the wrong Camera to use. Use ParallelCamera instead:
Camera camera = new ParallelCamera();
//camera.setRotationAxis(new Point3D(1, 1, 0));
//camera.setRotate(30);
scene.setCamera(camera);
also, how can I tell the subscene to take whatever space is available?
Change the type extended by MyView to something that is resizable and bind the size of the SubScene to the size of MyView:
private class MyView extends Pane {
MyView() {
...
setPrefSize(300, 300);
scene.widthProperty().bind(widthProperty());
scene.heightProperty().bind(heightProperty());
}
Related
I was working on JavaFX, and I realised something which kept me thinking. So, there's a scene in my constructor which adds the variable root of the type BorderPane. In the main class, this class' constructor is called first, thereby initialising the BorderPane in the Scene. However, the BorderPane is being modified and edited in a method called build. I am confused how the BorderPane is still being able to display its updated version from the build() since in the constructor it has already been commanded to display.
Here's my code:
public class SnacksOrDrinks
{
private BorderPane root;
private Stage primaryStage;
private Scene scene;
private Cart crt;
public SnacksOrDrinks(Stage primaryStage, Cart crt)
{
// BorderPane is being initialised and is being put inside the scene:
BorderPane root1 = new BorderPane();
this.scene = new Scene(root1, 800, 475);
this.crt = crt;
ImageView imgview = new ImageView("./application/MountainPainting.jpg");
imgview.fitWidthProperty().bind(primaryStage.widthProperty());
imgview.fitHeightProperty().bind(primaryStage.heightProperty());
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
this.primaryStage = primaryStage;
this.root = root1;
root.setStyle("-fx-background-color: blue");
root.getChildren().add(imgview);
}
public void build(Dispenser dis)
{
// ItemsPaneProp extends GridPane and stores the common properties shared in other classes.
ItemsPaneProp pane = new ItemsPaneProp();
// Header Created:
CommHeaderProp header = new CommHeaderProp("Lopes Vending Machine");
// However, BorderPane is being modified here, and the constructor has already been called.
BorderPane.setAlignment(header, Pos.CENTER);
root.setTop(header);
// Create a Line:
Line line = new Line(100, 10, 300, 10);
line.setFill(Color.GOLDENROD);
line.setStrokeWidth(2);
line.setSmooth(true);
pane.add(line, 0, 0);
// Create all the Scene's Components and setup the Event Handlers
ImageView img = new ImageView("./application/softdrink.jpg");
img.setFitWidth(75);
img.setFitHeight(75);
Button btn1 = new Button("Select to get Drinks", img);
btn1.setStyle("-fx-background-color: white");
btn1.setFont(Font.font("Times New Roman", FontWeight.BOLD, FontPosture.REGULAR, 12));
btn1.setContentDisplay(ContentDisplay.TOP);
pane.add(btn1, 0, 1);
ImageView img1 = new ImageView("./application/potato-chips.png");
img1.setFitWidth(75);
img1.setFitHeight(75);
Button btn2 = new Button("Select to get Snacks", img1);
btn2.setStyle("-fx-background-color: white");
btn2.setFont(Font.font("Times New Roman", FontWeight.BOLD, FontPosture.REGULAR, 12));
btn2.setContentDisplay(ContentDisplay.TOP);`enter code here`
pane.add(btn2, 1, 1);
root.setCenter(pane); // The BorderPane is being modified here too, but the constructor isn't updating the scene. Then, how does it still work?
btn1.setOnMouseClicked(new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent e)
{
// Create, build, and show Scene 2
DrinksList scene = new DrinksList(primaryStage, crt);
scene.build(dis);
scene.show();
}
});
btn2.setOnMouseClicked(new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent e)
{
// Create, build, and show Scene 2
SnacksList scene = new SnacksList(primaryStage, crt);
scene.build(dis);
scene.show();
}
});
}
public void show()
{// setting scene:
primaryStage.setScene(scene);
}
}
Take a look at https://docs.oracle.com/javase/8/javafx/scene-graph-tutorial/scenegraph.htm#JFXSG107
The JavaFX scene graph is a retained mode API, meaning that it maintains an internal model of all graphical objects in your application. At any given time, it knows what objects to display, what areas of the screen need repainting, and how to render it all in the most efficient manner.
Your borderPane - part of SceneGraph, so JavaFX knows, when you change anything in your borderPane, and automatically invoking redraw method for it.
I am working on a JavaFx application which use canvas to represent a diagram. The canvas is painting text using graphicsContext.fillText(). In the image below the canvas is on the right, on the left is a Label using the same font. My question is which renering parameter should I use to make the right text look the same as on the left ?
public class SampleRenderingIssue extends Application {
private final StackPane root = new StackPane();
private final Scene scene = new Scene(root, 300, 250);
private final BorderPane pane = new BorderPane();
private final Canvas canvas = new Canvas();
#Override
public void start(Stage stage) {
stage.setTitle("Sample Canvas");
VBox vbox = new VBox();
VBox.setVgrow(pane, Priority.ALWAYS);
vbox.getChildren().addAll( pane );
pane.getChildren().add(canvas);
root.getChildren().add(vbox);
stage.setScene(scene);
stage.sizeToScene();
setupCanvasPane();
stage.show();
Platform.runLater(()-> paint());
}
private void setupCanvasPane(){
canvas.widthProperty().bind(pane.widthProperty());
canvas.heightProperty().bind(pane.heightProperty());
pane.widthProperty().addListener((o,p,c)-> paint());
paint();
}
public void paint(){
GraphicsContext gr = canvas.getGraphicsContext2D();
gr.clearRect( 0,0, canvas.getWidth(), canvas.getHeight() );
gr.setFill( Color.web("#222222") );
gr.fillRect( 0,0,canvas.getWidth(), canvas.getHeight());
gr.setStroke( Color.WHITE );
gr.setFill( Color.WHITE );
gr.setLineWidth( 1d );
gr.strokeLine( 0,0, canvas.getWidth(), canvas.getHeight() );
gr.setFont( Font.font( "Candara"));
gr.fillText("This is a text", 100, 100 );
gr.setFont( Font.font( "Monospaced"));
gr.fillText("This is a text", 100, 120 );
}
public static void main(String[] args) {
launch(args);
}
}
The issue reproduces on a FullHd display where the Windows scale in Control Panel is set to 125% ( default value for a notebook ).
I also had poor text rendering quality in a Canvas. I display dense text in a small font size and it looked quite ugly before.
This may or may not be the same problem you have been running up against, since my Windows scaling is at 100%, but I've discovered in my case it was caused by the Canvas not using the same ClearType implementation as the rest of the UI widgets do. It seems to be set to GREY mode instead of LCD. It's a simple one line fix:
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFontSmoothingType(FontSmoothingType.LCD);
gc.setFont(Font.font("Consolas", 10));
gc.fillText("Example ABC 123", 10, 10);
Here is the result, in each case the top line is the rendered version, and the bottom line is a Label in the scene. I've included a zoomed in version, where you can clearly see Canvas is not doing the LCD subpixel rendering correctly
In the right handle example they appear to be identical, and my visual quality is restored.
I am a beginner who just recently started learning JavaFX, and I seem to be making the same reoccurring mistake within my programs. For example, in the following code I am trying to draw the X-Axis and Y-Axis and have it binded to half the width and height of the pane. When executed, the axes are very small and located at the topleft corner, but as you resize the window of the application, the axes slowly increase in size until the window not being resized anymore.
public class Debug extends Application {
#Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
GraphFunc test = new GraphFunc();
pane.getChildren().add(test);
Scene scene = new Scene(pane, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
class GraphFunc extends Pane {
private double xAxisSpan, yAxisSpan;
public GraphFunc() {
xAxisSpan = 100;
yAxisSpan = 100;
drawAxes();
}
private void drawAxes() {
Line xAxis = new Line(0, 50, 100, 50);
Line yAxis = new Line(50, 0, 50, 100);
xAxis.setStartX(0);
xAxis.startYProperty().bind(heightProperty().divide(2));
xAxis.endXProperty().bind(widthProperty());
xAxis.endYProperty().bind(heightProperty().divide(2));
yAxis.startXProperty().bind(widthProperty().divide(2));
yAxis.setStartY(0);
yAxis.endXProperty().bind(widthProperty().divide(2));
yAxis.endYProperty().bind(heightProperty());
getChildren().addAll(xAxis, yAxis);
}
}
I am confused because when pane is change to a StackPane, this does not happen. Also if I moved the code in drawAxes() to start() and added the lines to pane it would also not do this. Please explain, I cannot seem to understand what is happening after researching and playing around with it.
This is happening because of Pane. Pane is meant to be used when absolute positioning of children is required.
This is from Oracle documentation:
This class may be used directly in cases where absolute positioning of children is required since it does not perform layout beyond resizing resizable children to their preferred sizes. It is the application's responsibility to position the children since the pane leaves the positions alone during layout.
Pane pane = new Pane();
GraphFunc test = new GraphFunc();
pane.getChildren().add(test);
Scene scene = new Scene(pane, 500, 500);
If you remove the width and height from the scene and instead set the width and height directly on to test like this:
public void start(Stage primaryStage) {
GridPane pane = new GridPane();
GraphFunc test = new GraphFunc();
test.setPrefSize(500, 500);
pane.getChildren().add(test);
Scene scene = new Scene(pane);
primaryStage.setScene(scene);
primaryStage.show();
}
You will get this:
drawn image
However you will not be able to span.
From Oracle documentation:
Note: if an application needs children to be kept aligned within a
parent (centered, positioned at top-left, etc), it should use a
StackPane instead.
Also from Oracle documentation:
Pane does not clip its content by default, so it is possible that
childrens' bounds may extend outside its own bounds, either if
children are positioned at negative coordinates or the pane is resized
smaller than its preferred size.
I am working on an application that enables the user to draw graphs, i.e. edges and nodes. As nodes I am currently using plain JavaFX label elements. When drawing the edges, I need to consider the bounds of the label, however, the width and the height of the labels seem to be initialized only after "drawing" it. E.g., when starting the application the label bounds have a width/height of 0, but if a label is repositioned by the user, the width/height is correct.
Is there a possibility to force JavaFX to draw the current elements? The code is rather complex, but the following gives an idea of what I want to do:
stackpane = new StackPane();
text = new Label("Test");
text.setStyle("-fx-border-color:black; -fx-padding:3px;");
stackpane.getChildren().addAll(text);
...
// is it possible to force JavaFX to draw the text here?
...
// some calculations with the bounds of the label
Node node = getLabel();
Bounds bounds = node.getBoundsInParent();
double height = bounds.getHeight();
double width = bounds.getWidth();
I also tried to wrap the text in a rectangle and then manually set the width/height of the rectangle. This works, but the nodes have labels of different length and thus manually setting it is not always suitable.
You may try to bind your logic to node.boundsInParentProperty() changes
public class SmartBorder extends Application {
#Override
public void start(Stage primaryStage) {
final Label txt = new Label("Example");
txt.relocate(100, 100);
Pane root = new Pane();
final Rectangle border = new Rectangle();
border.setFill(Color.TRANSPARENT);
border.setStroke(Color.RED);
// here we autoupdate border
txt.boundsInParentProperty().addListener(new ChangeListener<Bounds>() {
#Override
public void changed(ObservableValue<? extends Bounds> ov, Bounds old, Bounds b) {
border.setX(b.getMinX() - 1);
border.setY(b.getMinY() - 1);
border.setWidth(b.getWidth()+2);
border.setHeight(b.getHeight()+2);
}
});
root.getChildren().addAll(txt, border);
// click to see automatic border reaction
root.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
txt.relocate(Math.random()*200, Math.random()*200);
txt.setText(Math.random() + "");
}
});
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
}
I want to resize an Rectangle shape from JavaFX accordingly with a TextField which automatically resize on Window resize using Anchors.
I tried add anchors to the rectangle also but it won't resize from the width I set from SceneBuilder.
So short story: when my TextField resizes because my Window resize the Rectangle should resize to the same width as the TextField. Thanks
You can bind the rectangle's width to the text field's:
myRectangle.widthProperty().bind(myTextField.widthProperty());
Example:
public class Demo extends Application {
#Override
public void start(Stage stage) {
Group root = new Group();
Scene scene = new Scene(root);
stage.setScene(scene);
TextField myTextField = new TextField("default");
Rectangle myRectangle = new Rectangle();
myRectangle.setHeight(30);
myRectangle.setFill(Color.AQUA);
myRectangle.widthProperty().bind(myTextField.widthProperty());
final VBox hb = new VBox(10);
hb.setPadding(new Insets(5));
hb.getChildren().addAll(myTextField, myRectangle);
scene.setRoot(hb);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}