FX Experience Has Gone Read-Only

I've been maintaining FX Experience for a really long time now, and I love hearing from people who enjoy my weekly links roundup. One thing I've noticed recently is that maintaining two sites (FX Experience and JonathanGiles.net) takes more time than ideal, and splits the audience up. Therefore, FX Experience will become read-only for new blog posts, but weekly posts will continue to be published on JonathanGiles.net. If you follow @FXExperience on Twitter, I suggest you also follow @JonathanGiles. This is not the end - just a consolidation of my online presence to make my life a little easier!

tl;dr: Follow me on Twitter and check for the latest news on JonathanGiles.net.

Resizable Grid using Canvas

Resizable Grid using Canvas

I was hacking on a application today and wanted a dot grid in the background. Canvas is well suited to this as you don’t need 1000s of nodes for every dot on the grid. Canvas by default has a fixed size so I wrapped it in a Pane to make it Resizable. I thought this might be a generally useful example to show how to create a resizable canvas node. Also if you ever need a grid, it could be applied to a normal line grid just as easily. So here is a little test app with a single node of my DotGrid.

 

Dot Gird Test App
Test Application

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

/**
* Test app for DotGrid
*/
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

/**
 * Test app for DotGrid
 */
public class DotGridTestApp extends Application {

    @Override public void start(Stage stage) throws Exception {
        stage.setScene(new Scene(new DotGrid(), Color.WHITE));
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Dot Grid Class


import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;

/**
 * A node that draws a triangle grid of dots using canvas
 */
public class DotGrid extends Pane {
    private static final double SPACING_X = 25;
    private static final double SPACING_Y = 20;
    private static final double RADIUS = 1.5;
    private Canvas canvas = new Canvas();

    public DotGrid() {
        getChildren().add(canvas);
    }

    @Override protected void layoutChildren() {
        final int top = (int)snappedTopInset();
        final int right = (int)snappedRightInset();
        final int bottom = (int)snappedBottomInset();
        final int left = (int)snappedLeftInset();
        final int w = (int)getWidth() - left - right;
        final int h = (int)getHeight() - top - bottom;
        canvas.setLayoutX(left);
        canvas.setLayoutY(top);
        if (w != canvas.getWidth() || h != canvas.getHeight()) {
            canvas.setWidth(w);
            canvas.setHeight(h);
            GraphicsContext g = canvas.getGraphicsContext2D();
            g.clearRect(0, 0, w, h);
            g.setFill(Color.gray(0,0.2));

            for (int x = 0; x < w; x += SPACING_X) {
                for (int y = 0; y < h; y += SPACING_Y) {
                    double offsetY = (y%(2*SPACING_Y)) == 0 ? SPACING_X /2 : 0;
                    g.fillOval(x-RADIUS+offsetY,y-RADIUS,RADIUS+RADIUS,RADIUS+RADIUS);
                }
            }
        }
    }
}

There really is not much too it, when the layout is applied to Pane check if size has changed and redraw the canvas. There are a couple of optimizations you could do here if performance was a issue.

  • When the Pane gets bigger don’t clear, just draw any new dots needed. If you were animating the size growing then this might be a good win.
  • Draw the dot once into a WritableImage using another Canvas or Circle node and snapshot(). Then draw that image for each dot. That way turn out to be a performance win, but maybe not. If you had more complex dots, like company logos then this would probably be a good gain.

In this case I have not set any preferred size for the DotGrid so it will be whatever size its parent wants it to be. If you need it to be a minimum size you could call setPrefSize() or setMinSize() in the constructor.

Announcing SceneBuilder 2.0

Announcing SceneBuilder 2.0

Amazing news everyone, SceneBuilder 2.0 is released today. It has many cool new features and was a complete rewrite from the ground up so that it can be split into parts and embedded in your favorite IDE.

Download

Mo has put up a great video tutorial for SceneBuilder 2 on YouTube: Watch

Scene Builder 2

Whats New

  • JavaFX Scene Builder Kit. JavaFX Scene Builder Kit is an API that allows the integration of Scene Builder panels and functionalities directly into the GUI of a larger application, or a Java IDE, such as NetBeans, IntelliJ, and Eclipse. See Working with the JavaFX Scene Builder Kit for more details.
  • Ability to add custom GUI components to the Library. Custom components imported from a third party JAR file can now be added permanently to the Library of available GUI components. See Adding Custom Components to the Library for more information.
  • Support for new JavaFX 8 UI components. The new UI components, TreeTableView, DatePicker, and SwingNode, that were introduced in the Java SE 8 are now supported in Scene Builder 2.0. To see the list of JavaFX 8 UI components that are available, type FX8 in the Library panel’s search text field.
  • 3D Support. FXML documents containing 3D objects can now be loaded and saved in the Scene Builder tool. You can view and edit properties of the 3D objects using the Inspector panel. You can not, however, create new 3D objects using the Scene Builder tool. See JavaFX Scene Builder User Guide for more information.
  • Support for Rich Text. A new container, TextFlow, is now available in the Library of GUI components. You can drag multiple text nodes and other types of nodes, into the a TextFlow container. You can also directly manipulate the text nodes to re-arrange them in the container. Inline and property editing features are also available for each text node.
  • Library Panel Improvements. The default viewing option for the Library panel is a new feature that gives you the option to view all the component categories as collapsible sections.
  • Hierarchy Panel Improvements. It is now more flexible to re-parent objects from one container to another container. It is also possible to drag and drop an object from the Hierarchy Panel to the Content Panel, and vice versa. You can also now select multiple objects in the Hierarchy Panel in order to re-parent them.
  • Content Panel Improvements. You can now manipulate the components of an HBox, VBox, FlowPane, or Toolbar container directly in the Content Panel to re-order them. You can also insert a new component at any position in the container. Note that this direct manipulation is currently not available for the TilePane container.
  • Inspector Panel Improvements. Many property editors in the Inspector panel use the Suggested List dialog window, which displays a list of selectable values from which you can make a selection instead of manually entering them. For example, the CSS Style editor provides a list of applicable CSS properties, based on the currently selected object(s). List handling has also been simplified. Multi-selection of components of different types (i.e. Button + TextField) is now supported. In addition, the usability of many of the property editors, such as AnchorPane Constraints, Rotate, and Padding, have been improved.
  • Preview Window Improvements. The content in the Preview window is now automatically refreshed as the current FXML document is being edited. Hence, there’s no need to use the Refresh command in order to preview the modified UI layout.
  • Change in the Include FXML Command. When you click File from the Menu bar and then click Include, you can only select the FXML menu item if the current FXML document you are editing is saved on your system’s disk and is not empty.

 

JavaFX HD Menus on RaspberryPi

JavaFX HD Menus on RaspberryPi

We put together a demo that shows what JavaFX can do on a RaspberryPi running fill 1080p HD on a TV using 5 way navigation(Arrows + Select). I hope you enjoy it, we had a lot of fun making it.

The first section of the video is a real recording direct of the HDMI output of the Raspberry Pi. So you can see the raw performance of the device, though video capture was limited to 30fps when the Pi was rendering at 60fps much of the time. The second section is a demo of how SceneBuilder could be used to build one of the demos. In all the menus we show the little overlay of arrow keys in top right corner so you can see how the menu is being navigated.

There are 4 separate menu demos:

  1. The first menu is a classic 2D menu system with a cool 2.5D section chooser.
  2. This is a cool vector 2D animated menu with a fun visual style. Playing with the idea of rotation.
  3. This is a cartoon retro style 3D menu showing 3D extruded text and 3D modeled TVs. The text and TVs were created in Cheetah 3D and exported as OBJ then imported using the OBJ importer available in the open source 3D Viewer sample app. In this demo and the next we have random animated lighting in and the ability to spin the 3D model with the <- and -> arrow keys so that the user can get a feeling for it being real time rendered 3D rather than video of offline rendered content.
  4. This was a way out 3D menu featuring DukeBot who was a early alternative design for the Java Duke mascot that did not get chosen. He was modeled and animated by John Yoon in Maya and we then imported the Maya ASCII file directly with all animation into JavaFX. The code for this menu is pretty tiny as its mostly working off the imported Maya file. The Maya importer is also open source and in the 3D Viewer sample app.

Its mostly running on the shipping EA of JavaFX 8 Embedded we prototyped a couple changes to the platform that we are working on making them real and I hope they will make it into 8 but not sure yet if we will have time. The changes are some performance improvements to how we draw into frame buffer, also the ability to draw JavaFX with transparent background on a hardware layer over hardware decoded video.

3D SpaceNavigator with JavaFX

I just got a 3D Connexion SpaceNavigator which is a kind of 3D input device(mouse/stick). It is cool to use when 3D modeling content for JavaFX but I thought it would be even better if I could navigate my JavaFX 3D scenes using it. I managed to hack some quick code to get it working in JavaFX. Many thanks to the JInput project, they made it super easy. Its super fun so I recoded a little video to share with you.

SpaceNavigator with JavaFX from Jasper Potts on Vimeo.

The code really is very simple, I just have a AnimationTimer that every frame checks to get the current inputs from device and applies them to the camera transforms. The device via jinput provides 6 floats for each axis and 2 booleans for the buttons, so could not be easier to connect to your app. Below is complete 3D app with a simple cube. I will be working on getting the object importers out in open source for you to use very soon 🙂

public class InputTestBlog extends Application {
    private ControllerEnvironment controllerEnvironment;
    private Controller spaceNavigator;
    private Component[] components;
    private Translate translate;
    private Rotate rotateX,rotateY,rotateZ;

    @Override public void start(Stage stage) throws Exception {
        controllerEnvironment = ControllerEnvironment.getDefaultEnvironment();
        Controller[] controllers = controllerEnvironment.getControllers();
        for(Controller controller: controllers){
            if ("SpaceNavigator".equalsIgnoreCase(controller.getName())){
                spaceNavigator = controller;
                System.out.println("USING Device ["+controller.getName()+"] of type ["+controller.getType().toString()+"]");
                components = spaceNavigator.getComponents();
            }
        }

        Group root = new Group();
        Scene scene = new Scene(root, 1024, 768, true);
        stage.setScene(scene);
        scene.setFill(Color.GRAY);
        // CAMERA
        final PerspectiveCamera camera = new PerspectiveCamera(true);
        scene.setCamera(camera);
        root.getChildren().add(camera);
        // BOX
        Box testBox = new Box(5,5,5);
        testBox.setMaterial(new PhongMaterial(Color.RED));
        testBox.setDrawMode(DrawMode.LINE);
        root.getChildren().add(testBox);
        // MOVE CAMERA
        camera.getTransforms().addAll(
                rotateY = new Rotate(-20, Rotate.Y_AXIS),
                rotateX = new Rotate(-20, Rotate.X_AXIS),
                rotateZ = new Rotate(0, Rotate.Z_AXIS),
                translate = new Translate(5, -5, -15)
        );
        // SHOW STAGE
        stage.show();
        // CHECK FOR INPUT
        if (spaceNavigator != null) {
            new AnimationTimer() {
                @Override public void handle(long l) {
                    if (spaceNavigator.poll()) {
                        for(Component component: components) {
                            switch(component.getName()) {
                                case "x":
                                    translate.setX(translate.getX() + component.getPollData());
                                    break;
                                case "y":
                                    translate.setY(translate.getY()+component.getPollData());
                                    break;
                                case "z":
                                    translate.setZ(translate.getZ()+component.getPollData());
                                    break;
                                case "rx":
                                    rotateX.setAngle(rotateX.getAngle()+component.getPollData());
                                    break;
                                case "ry":
                                    rotateY.setAngle(rotateY.getAngle()+component.getPollData());
                                    break;
                                case "rz":
                                    rotateZ.setAngle(rotateZ.getAngle()+component.getPollData());
                                    break;
                            }
                        }
                    }
                }
            }.start();
        }
    }

    public static void main(String[] args) {
        System.setProperty("net.java.games.input.librarypath", new File("lib").getAbsolutePath());
        launch(args);
    }
}