JavaFX8 presentation view (duplicate pane and content) - javafx

I'm trying to create a presentation view for my Drawing app, where only the drawing board (pane) is visible. So the user can show the presentation view on the projector while having the actual drawing pane and tools on the PC.
My current approach is to create a Snapshot from the pane in each frame and display it in an ImageView on the second stage.
public void startStream(){
new AnimationTimer() {
#Override
public void handle(long now) {
WritableImage image = drawingPane.snapshot(new SnapshotParameters(), null);
stream.setImage(image);
}
}.start();
final Screen screen = Screen.getPrimary();
final Stage stage = new Stage();
StackPane root = new StackPane();
root.getChildren().add(stream);
Scene scene = new Scene(root, drawingPane.getWidth(), drawingPane.getHeight());
stage.setScene(scene);
stage.setTitle("streaming stage");
Rectangle2D bounds = screen.getBounds();
System.out.println(bounds);
stage.setX(bounds.getMinX() + (bounds.getWidth() - 300) / 2);
stage.setY(bounds.getMinY() + (bounds.getHeight() - 200) / 2);
stage.show();
}
problem here is it's eating a lot of RAM, like 800MB and also 30% more CPU usage. I can understand, that creating an Image on every frame is not efficient, that's why I'd like to ask, if there is a way to get this working more efficiently.
Also if there is a better approach/solution to this feature, I'd be thankful for it.

To reduce the CPU usage, do not make a snapshot in an AnimationTimer—this really makes a snapshot at the frame rate (60 FPS) frequency, even if nothing has changed in the drawingPane. Instead, make the snapshot after drawingPane's layout. Adding a listener to drawingPane's needsLayoutProperty should do the trick:
drawingPane.needsLayoutProperty().addListener((obs, wasDirty, isDirty) -> {
if(!isDirty) {
WritableImage image = drawingPane.snapshot(new SnapshotParameters(), null);
stream.setImage(image);
}
});
This should lower the CPU usage when you are not editing the drawingPane.
It may also lower the memory usage, because garbage collector may kick in faster and collect the old snapshots. If not, consider re-using the WritableImage instance, if it already has the correct size:
WritableImage image = null;
drawingPane.needsLayoutProperty().addListener((obs, wasDirty, isDirty) -> {
if(!isDirty) {
if(this.image != null &&
(this.image.getWidth() != drawingPane.getWidth() ||
this.image.getHeight() != drawingPane.getHeight())) {
this.image = null;
}
this.image = drawingPane.snapshot(new SnapshotParameters(), this.image);
stream.setImage(this.image);
}
});
Note that this answer assumes that children of drawingPane are managed children (which is the default), and thus their layout being marked as dirty causes the drawingPane's layout being marked as dirty, so that you can actually observe any changes of the needsLayout property.

Related

Scaling start screen with fullscreen JavaFX

I'm stuck on full scaling for my JavaFX application. I'm in the process of making a full screen feature for the application and I'm running into issues on trying to get the aspect ratio and positioning right without manually editing the values.
With the way I've been trying, the values butcher the game's start screen making the positioning change making the designs of the game offset from the center of the application. I can understand the reasoning behind it with the way I set it up. My problem is wondering how to scale the start screen and keep it's original position without having to manually edit the values.
What I thought of was trying to input the value and having it scale according to that value then putting the result in the position of objects X and Y.
if (fullscreen) {
WIDTH = (Enter aspect ratio here) * 1.5;
HEIGHT = (Enter aspect ratio here) * 1.5;
} else {
WIDTH = 990;
HEIGHT = 525;
}
with Obvious flaws this butchers the start screen.
My solution was to make a double() that you just enter the value of the application WIDTH/HEIGHT then entering the amount you want to divide by (since I couldn't come up with exact cords, I grabbed the WIDTH and divided by specific value for it to align in the center) following with a boolean to state whether it's full screened or not. Though my only issue with this theory is that it'll only work with 1920x1080 monitors so I'd assume I would have to manually enter all types of aspect ratios to make it fit otherwise the start screen would be butchered.
I've seen a way of scaling here:
JavaFX fullscreen - resizing elements based upon screen size
Though I'm not sure how to correctly implement it.
public static boolean fullscreen = false;
public static double WIDTH = 990;
public static double HEIGHT = 525;
public static Pane pane = new Pane();
public static void StartScreen() {
pane.setPrefSize(WIDTH, (HEIGHT - 25)); // the 25 is for the text field/input.
pane.setStyle("-fx-background-color: transparent;");
Group sGroup = new Group();
Image i = new Image("file:start/So7AA.png");
ImageView outer = new ImageView(i);
// outer.setX(Ce.WIDTH/4.75); //4.75 // The functioning code for the snippit
// outer.setY(-10); //-10
outer.setX(Ce.WIDTH/position(3.60, fullscreen)); //4.75 // The non functioning code.
outer.setY(position(-1, Ce.fullscreen)); //-10
outer.setFitWidth(550);
outer.setFitHeight(550);
outer.setOpacity(.3);
GaussianBlur gBlur = new GaussianBlur();
gBlur.setRadius(50);
ImageView seal = new ImageView(i);
// seal.setX(Ce.WIDTH/3.83); //247.5 - 3.83
// seal.setY(39); //39
seal.setX(Ce.WIDTH/position(3.83, fullscreen)); //247.5 - 3.83
seal.setY(position(32, Ce.fullscreen)); //39
seal.setFitWidth(450);
seal.setFitHeight(450);
ImageView sealBlur = new ImageView(i);
// sealBlur.setX(Ce.WIDTH/3.83); //247.5 - 3.83
// sealBlur.setY(39); //39
sealBlur.setX(Ce.WIDTH/position(3.83, fullscreen)); //247.5 - 3.83
sealBlur.setY(position(32, Ce.fullscreen));
sealBlur.setFitWidth(450);
sealBlur.setFitHeight(450);
sealBlur.setEffect(gBlur);
}
For getting the values of the WIDTH and HEIGHT:
public static double getWidth(double W, boolean fs) {
if (fs) {
return WIDTH = Screen.getPrimary().getBounds().getMaxX();
} else {
return WIDTH = W;
}
}
public static double getHeight(double H, boolean fs) {
if (fs) {
return HEIGHT = Screen.getPrimary().getBounds().getMaxY();
} else {
return HEIGHT = H;
}
}
I know there's a way around this, I'm just not sure how to pull it off.
I'm not sure exactly what the requirements are here, but it looks like you have three images, which you want centered, and you want them all scaled by the same amount so that one of the images fills the available space in its container. (Then, you just need to make sure its container grows to fill all the space, and you can call stage.setFullScreen(true) or stage.setMaximized(true) as needed.)
You can do this with a pretty simple custom pane that manages the layout in the layoutChildren() method:
public class ImagePane extends Region {
private final Image image1;
private final ImageView imageView1;
private final Image image2;
private final ImageView imageView2;
private final Image image3;
private final ImageView imageView3;
public ImagePane(Image image1, Image image2, Image image3) {
this.image1 = image1;
this.image2 = image2;
this.image3 = image3;
imageView1 = new ImageView(image1);
imageView2 = new ImageView(image2);
imageView3 = new ImageView(image3);
getChildren().addAll(imageView1, imageView2, imageView3);
}
#Override
protected void layoutChildren() {
double xScale = getWidth() / image1.getWidth();
double yScale = getHeight() / image1.getHeight();
double scale = Math.min(xScale, yScale);
for (ImageView view : List.of(imageView1, imageView2, imageView3) {
scaleAndCenter(view, scale);
}
}
private void scaleAndCenter(ImageView view, scale) {
double w = scale * view.getImage().getWidth();
double h = scale * view.getImage().getHeight();
view.setFitWidth(w);
view.setFitHeight(h);
view.relocate((getWidth()-w) / 2, (getHeight()-h) / 2);
}
}
The rest of your layout looks something like:
Label label = new Label("Type in 'start'.\nType in 'options' for options.\n(Demo)");
TextField textField = new TextField();
ImagePane imagePane = new ImagePane(new Image(...), new Image(...), new Image(...));
AnchorPane anchor = new AnchorPane(imagePane, label);
AnchorPane.setTopAnchor(imagePane, 0.0);
AnchorPane.setRightAnchor(imagePane, 0.0);
AnchorPane.setBottomAnchor(imagePane, 0.0);
AnchorPane.setLeftAnchor(imagePane, 0.0);
AnchorPane.setTopAnchor(label, 5.0);
AnchorPane.setLeftAnchor(label, 5.0);
BorderPane root = new BorderPane();
root.setCenter(anchor);
root.setBottom(textField);
Now everything should just respond to whatever size is assigned to the root pane, so setting full screen mode should "just work".

JavaFX keyboard input stops working after adding buttons

I am building a game engine as a school project. When I add button to the same group as where I have my canvas, I can't control my player anymore with keyboard. Buttons and everything else still works like normal.
My code is pretty huge, so this is a simplified code of the problem:
public abstract class GEngine extends Application {
public void start(Stage theStage) throws Exception {
try {
this.stage = theStage;
Group root = new Group();
Scene theScene = new Scene(root);
stage.setScene(theScene);
Canvas canvas = new Canvas(windowWidth, windowHeight);
root.getChildren().add(canvas);
Button btn = new Button("new");
btn.setOnMousePressed(e->System.out.println("press"));
root.getChildren().add(btn);
Timeline gameLoop = new Timeline();
gameLoop.setCycleCount(Timeline.INDEFINITE);
// Handle input
theScene.setOnKeyPressed(Input::handlePressed);
theScene.setOnKeyReleased(Input::handleReleased);
// Control game loop and it's speed
KeyFrame kf = new KeyFrame(
Duration.seconds(0.017), // 60 FPS
(e)->this.controlGameLoop());
gameLoop.getKeyFrames().add( kf );
gameLoop.play();
stage.show();
} catch (Exception e) {
e.printStackTrace();
stop();
}
}
}
There is probably something happening on the background which I just don't understand. I can show my Input class code too if somebody wants to see it, but in my understanding it's not necessary.
I have tried using AnchorPane as main root and make a separate groups for buttons and canvas which I add the to the AnchorPane, but that did not help at all. That was pretty much the only offered solution I could find from google.
Adding btn.setFocusTraversable(false); fixed the problem, thanks to Luxusproblem for providing the answer!

Modifying interface with key interactions in javafx

I'm trying to code a 2048 game using JavaFX and I'm facing a problem.
#Override
public void start(Stage primaryStage){
primaryStage.setResizable(false);
Scene scene = new Scene(firstContent());
primaryStage.setScene(scene);
primaryStage.show();
scene.setOnKeyPressed(new EventHandler<KeyEvent>(){
#Override
public void handle(KeyEvent e){
KeyCode key = e.getCode();
if((key.equals(KeyCode.UP))){
System.out.println("recieved UP");
Scene scene = new Scene(createContent());
primaryStage.setScene(scene);
primaryStage.show();
} else if(key.equals(KeyCode.DOWN)){
System.out.println("recieved DOWN");
}
}
});
}
So here I open my window initialised with firstContent (basically it creates an array of empty tiles, and fills two of them with 2 or 4 randomly), display it and start listening for key presses. The idea is to have a behavior for each arrow key (UP DOWN LEFT RIGHT) which will move the tiles accordingly. This is done by the following createContent() method :
public Parent createContent(){
String c = "";
List<Integer> known = new ArrayList<Integer>();
Pane root = new Pane();
root.setPrefSize(740, 700);
Random rand = new Random();
int pos1 = rand.nextInt(15);
if(tiles.get(pos1) != new Tile("")){
known.add(pos1);
pos1 = rand.nextInt(15);
if(known.contains(pos1)){
known.add(pos1);
pos1 = rand.nextInt(15);
}
}
for(int i = 0; i < NB_TILES; i++){
tiles.add(new Tile(c));
}
tiles.set(pos1, new Tile("2048"));
for(int i = 0; i < tiles.size(); i++){
// boring stuff to set the tile display to the right size
}
return root;
}
Now for the problem : when the application is running, if I press the down arrow, I do get on my terminal the "recieved DOWN" text as many times as I press the key as expected. But if I press the up arrow, the application will only recieve it once and the application seems to be frozen (meaning if I press down again, nothing happens).
As you could guess, I want to be able to call my method for each key press to be able to move my tiles around and utltimately combine them to get a playable version of 2048... Anyone know why my app gets frozen ?
If needed, I can provide other bits of code but I think I provided the essential. Just know that firstContent() works basically the same as createContent for now except it genereates two random numbers to get the first tiles of the game.
Thanks in advance for your help.

JavaFX - Progress properties don't work?

I'm trying to get the progress value of my Picture Viewer when another picture is loading. I've tried two simple ways to do it, but it didn't work out for me.
First I was using the progressProperty from the Image class:
public boolean nextImageClicked()
{
if(PICTURE_INDEX < picturePaths.size() - 1)
{
String path = picturePaths.get(PICTURE_INDEX + 1).toURI().toString();
Image newImage = new Image(path);
newImage.progressProperty().addListener((observable, oldValue, newValue) -> System.out.println("Current progress: "+newValue));
GUI.getImageView().setImage(newImage);
adjustImageViewBounds();
PICTURE_INDEX += 1;
return true;
}
else return false;
}
It didn't print out anything on the console because the progress value doesn't change bizarrely. So I've tried to put all the work stuff in a Task<Void> and getting the progress value through the Task:
public boolean nextClicked()
{
if(PICTURE_INDEX < picturePaths.size() - 1)
{
Task<Void> task = new Task<Void>()
{
#Override protected Void call() throws Exception
{
String path = picturePaths.get(PICTURE_INDEX + 1).toURI().toString();
Image newImage = new Image(path);
GUI.getImageView().setImage(newImage);
adjustImageViewBounds();
PICTURE_INDEX += 1;
return null;
}
};
task.setOnRunning(e -> System.out.println(task.getProgress()));
task.progressProperty().addListener((observable, oldValue, newValue) ->
{
System.out.println(newValue);
});
task.run();
return true;
}
else return false;
}
Also didn't work out as hoped.
task.setOnRunning(e -> System.out.println(task.getProgress()));
I implemented this to see the default value, it printed out "-1".
What have I to change to let the console return single progress values like "0.1", "0.14" ?
You need the Image to load in the background, so that the call to the constructor returns before the image is completely loaded. By default, it will block until it is loaded (so the progress property will be 1 by the time you add the listener to it):
public boolean nextImageClicked()
{
if(PICTURE_INDEX < picturePaths.size() - 1)
{
String path = picturePaths.get(PICTURE_INDEX + 1).toURI().toString();
// note additional parameter:
Image newImage = new Image(path, true);
newImage.progressProperty().addListener((observable, oldValue, newValue) -> System.out.println("Current progress: "+newValue));
GUI.getImageView().setImage(newImage);
adjustImageViewBounds();
PICTURE_INDEX += 1;
return true;
}
else return false;
}
For a Task's progress to change, you need to explicitly call updateProgress(...) on the task. The only way to know what to pass in would be to observe the image's progress and pass it to the task's progress, so you would just have a more convoluted version of the code above. This is not a good use case for a task, since Image already supports background loading out of the box.
Don't try to do this on your own. You've got no idea, how much of the image has been loaded, unless you find the size of the image before loading and load the image from a steam observing the progress of the stream, which would be unnecessarily complicated. BTW: The Image constructor you use returns when the image is completely loaded. You can specify the image to be loaded asynchronically by using the right constructor however. Image provides a progress property to observe the loading progress:
#Override
public void start(Stage primaryStage) {
ImageView iv = new ImageView();
ProgressBar pb = new ProgressBar();
Button btn = new Button("Load Image");
btn.setOnAction((ActionEvent event) -> {
// ca. 6 MB image loaded from web
Image image = new Image("http://eoimages.gsfc.nasa.gov/images/imagerecords/79000/79793/city_lights_africa_8k.jpg", true);
pb.progressProperty().bind(image.progressProperty());
iv.setImage(image);
});
ScrollPane sp = new ScrollPane(iv);
VBox.setVgrow(sp, Priority.ALWAYS);
VBox root = new VBox(btn, pb, sp);
root.setFillWidth(true);
Scene scene = new Scene(root);
primaryStage.setMaximized(true);
primaryStage.setScene(scene);
primaryStage.show();
}

JavaFX 2.X - Animated background and animated controls

A few days ago I started studying JavaFX, and came across the desire to perform 2 experiments. Firstly, I would like to know if it is possible to put an animated background behind an user interface. I've succeeded in creating an animated background, and now I'm having great difficulties to position some controls in the middle of my interface.
I'd like to introduce you 2 pictures of my program. The first demonstrates the undesirable result that I'm getting:
I believe this is my nodes tree:
This is the code of my application:
public class AnimatedBackground extends Application
{
// #########################################################################################################
// MAIN
// #########################################################################################################
public static void main(String[] args)
{
Application.launch(args);
}
// #########################################################################################################
// INSTÂNCIAS
// #########################################################################################################
private Group root;
private Group grp_hexagons;
private Rectangle rect_background;
private Scene cenario;
// UI
private VBox lay_box_controls;
private Label lab_test;
private TextArea texA_test;
private Button bot_test;
// #########################################################################################################
// INÍCIO FX
// #########################################################################################################
#Override public void start(Stage stage) throws Exception
{
this.confFX();
cenario = new Scene(this.root , 640 , 480);
this.rect_background.widthProperty().bind(this.cenario.widthProperty());
this.rect_background.heightProperty().bind(this.cenario.heightProperty());
stage.setScene(cenario);
stage.setTitle("Meu programa JavaFX - R.D.S.");
stage.show();
}
protected void confFX()
{
this.root = new Group();
this.grp_hexagons = new Group();
// Initiate the circles and all animation stuff.
for(int cont = 0 ; cont < 15 ; cont++)
{
Circle circle = new Circle();
circle.setFill(Color.WHITE);
circle.setEffect(new GaussianBlur(Math.random() * 8 + 2));
circle.setOpacity(Math.random());
circle.setRadius(20);
this.grp_hexagons.getChildren().add(circle);
double randScale = (Math.random() * 4) + 1;
KeyValue kValueX = new KeyValue(circle.scaleXProperty() , randScale);
KeyValue kValueY = new KeyValue(circle.scaleYProperty() , randScale);
KeyFrame kFrame = new KeyFrame(Duration.millis(5000 + (Math.random() * 5000)) , kValueX , kValueY);
Timeline linhaT = new Timeline();
linhaT.getKeyFrames().add(kFrame);
linhaT.setAutoReverse(true);
linhaT.setCycleCount(Animation.INDEFINITE);
linhaT.play();
}
this.rect_background = new Rectangle();
this.root.getChildren().add(this.rect_background);
this.root.getChildren().add(this.grp_hexagons);
// UI
this.lay_box_controls = new VBox();
this.lay_box_controls.setSpacing(20);
this.lay_box_controls.setAlignment(Pos.CENTER);
this.bot_test = new Button("CHANGE POSITIONS");
this.bot_test.setAlignment(Pos.CENTER);
this.bot_test.setOnAction(new EventHandler<ActionEvent>()
{
#Override public void handle(ActionEvent e)
{
for(Node hexagono : grp_hexagons.getChildren())
{
hexagono.setTranslateX(Math.random() * cenario.getWidth());
hexagono.setTranslateY(Math.random() * cenario.getHeight());
}
}
});
this.texA_test = new TextArea();
this.texA_test.setText("This is just a test.");
this.lab_test = new Label("This is just a label.");
this.lab_test.setTextFill(Color.WHITE);
this.lab_test.setFont(new Font(32));
this.lay_box_controls.getChildren().add(this.lab_test);
this.lay_box_controls.getChildren().add(this.texA_test);
this.lay_box_controls.getChildren().add(this.bot_test);
this.root.getChildren().add(this.lay_box_controls);
}
}
I've tried to make the use of a StackPane as the root of my scene graph, but also found an undesired result. Despite the controls have stayed in the center of the window, the circles begin to move in as they grow and shrink, making it appear that everything is weird.
The second thing I would like to know is if it is possible to customize the controls so they perform some animation when some event happens. Although we can change the appearance of controls using CSS, it's harder to create something complex. For example, when a control changes its appearance due to a change of state, the transition state change is not made in an animated way, but in an abrupt and static way. Is there a way to animate, for example, a button between its states? This would be done using the JavaFX API? Or would that be using CSS? Or would not be possible in any way?
Thank you for your attention.
after much struggle, I and some users of the Oracle community could resolve this issue. I see no need to repeat here all the resolution made ​​by us, so I'll post the link so you can access the solution of the problem. I hope this benefits us all. Thanks for your attention anyway.
https://community.oracle.com/thread/2620500

Resources