///usr/bin/env jbang "$0" "$@" ; exit $? //DEPS org.openjfx:javafx-controls:22.0.2 //DEPS org.openjfx:javafx-graphics:22.0.2:${os.detected.jfxname} import javafx.animation.AnimationTimer; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.application.Application; import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.Scene; import javafx.scene.layout.Pane; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; import javafx.stage.Stage; import javafx.util.Duration; import java.util.Random; /** * Execute with a Java runtime with JavaFX included, and JBang: * jbang FxDemo.java * * A demo is available in this movie: * https://www.youtube.com/watch?v=XhDQvkcYJ88 */ public class FxDemo extends Application { private static int NUMBER_OF_BALLS = 10_000; private Random r = new Random(); private Scene scene; private long lastUpdate = 0; @Override public void start(Stage stage) { var javaVersion = System.getProperty("java.version"); var javaVendor = System.getProperty("java.vendor"); var javaVendorVersion = System.getProperty("java.vm.version"); var javafxVersion = System.getProperty("javafx.version"); VBox holder = new VBox(); holder.setFillWidth(true); holder.setAlignment(Pos.TOP_CENTER); holder.setSpacing(5); holder.getChildren().addAll( new Label("Java: " + javaVersion + ", " + javaVendor + ", " + javaVendorVersion), new Label("JavaFX: " + javafxVersion), getFramerateLabel(), getBouncingBallPane() ); scene = new Scene(holder, 400, 700); stage.setTitle("JavaFX Demo"); stage.setScene(scene); stage.show(); } public static void main(String[] args) { launch(); } private Label getFramerateLabel() { Label frameRateLabel = new Label(); AnimationTimer frameRateMeter = new AnimationTimer() { @Override public void handle(long now) { if (lastUpdate > 0) { long nanosElapsed = now - lastUpdate; double frameRate = 1_000_000_000.0 / nanosElapsed; frameRateLabel.setText(String.format("Current frame rate: %.3f", frameRate)); } lastUpdate = now; } }; frameRateMeter.start(); return frameRateLabel; } private Pane getBouncingBallPane() { Pane pane = new Pane(); pane.setPrefWidth(Double.MAX_VALUE); pane.setPrefHeight(Double.MAX_VALUE); pane.setMinHeight(100.0); for (var i = 0; i < NUMBER_OF_BALLS; i++) { pane.getChildren().add(new Ball()); } return pane; } class Ball extends Circle { private double dx = r.nextInt(1, 5); private double dy = r.nextInt(1, 5); private int size = r.nextInt(1, 15); private Color randomColor = Color.color(Math.random(), Math.random(), Math.random()); public Ball() { this.setRadius(size); this.setFill(randomColor); relocate(r.nextInt(380), r.nextInt(620)); Timeline timeline = new Timeline(new KeyFrame(Duration.millis(5), t -> { if (hitRightOrLeftEdge()) { dx *= -1; // Ball hit right or left wall, so reverse direction } if (hitTopOrBottom()) { dy *= -1; // Ball hit top or bottom, so reverse direction } setLayoutX(getLayoutX() + dx); setLayoutY(getLayoutY() + dy); })); timeline.setCycleCount(Timeline.INDEFINITE); timeline.play(); } private boolean hitRightOrLeftEdge() { return (getLayoutX() < (scene.getX() + getRadius())) || (getLayoutX() > (scene.getWidth() - getRadius())); } private boolean hitTopOrBottom() { return (getLayoutY() < (scene.getY() - getRadius())) || (getLayoutY() > (scene.getHeight() - getRadius() - 60)); } } }