GroupLayout for JavaFX 8: GroupLayoutPane

I recently decided to do small gui application in JavaFX, and I'm very excited about it's capabilities and nice design. Compared to pure-Swing gui, JavaFX seems to be more consistent and extendable.

But as usual, there's a fly in the oinment - default layouts are really horrible for anything except simplest setups. There are eight different layouts, all with incredible amount of hidden gotchas.

Take VBox for example. By default, it doesn't expand children to fill all the provided width. There's an option for fixing that, of course. Okay, but now I want to spread VBox children vertically. Sure there is fillHeight property to give me that? Nope. Screw me. Maybe I can use GridPane for that? Yes, I can, but first I need to learn all the setValignment, setHgrow, and a bunch of other cryptic methods. And whatever I did, I was unable to place a canvas onto that grid and make it resize/redraw when the frame is resized.

I really missed the old GroupLayout from Swing. For some reason, it's always viewed as not usable by humans, instead only automated gui builders should target that API. By that definition, I am the automated gui builder - I always found GroupLayout to be the simplest to use and reason about. You just set min/pref/max sizes for components, place them in proper order, and you're done - there are no gotchas awaiting you.

So I decided to port GroupLayout to JavaFX. Turns out, it's not hard - horizontal and vertical calculations are completely decoupled, and internal JavaFX layout code is really simple to understand and use.

Basic layout manager template looks like the following:
// Pane is a superclass for all layout managers
public class GroupLayoutPane extends Pane {
    // these methods will signal required sizes to parent containers
    @Override protected double computeMinWidth(double width) { /*...*/ }
    @Override protected double computePrefWidth(double width) { /*...*/ }
    @Override protected double computeMaxWidth(double width) { /*...*/ }
    @Override protected double computeMinHeight(double width) { /*...*/ }
    @Override protected double computePrefHeight(double width) { /*...*/ }
    @Override protected double computeMaxHeight(double width) { /*...*/ }

    private boolean performingLayout = false;

    @Override public void requestLayout() {
        if (performingLayout) {
            return;
        }
        super.requestLayout();
    }

    @Override protected void layoutChildren() {
        performingLayout = true;

        // actual layout code
        // calculate children bounds,
        // then use Node.resize(width, height) and Node.relocate(x, y) to apply them
        // don't forget to handle insets here!

        performingLayout = false;
    }
}
Here's the class structure for actual layout calculations (mostly copied from original GroupLayout source):
// Springs store min/pref/max size, calculate and apply sizes to their children
abstract class Spring {
    // we don't want to ask child components about their size too often,
    // so this method ensures that we do it only once per layout and cache the results
    abstract void recalculateSizes();

    abstract double getMinSize();
    abstract double getPrefSize();
    abstract double getMaxSize();

    // apply the actual resizing to the elements
    abstract void resizeRelocate(double location, double size);
}

// Simple gap without content
// Gap with min=0, pref=0 and max=Short.MAX_VALUE is called "glue"
class Gap extends Spring {}

// Spring that wraps some node
class NodeWrapper extends Spring {}

// Now we get to the meat of stuff
abstract class Group extends Spring {
    public Group addGap(double size) { /*...*/ }
    public Group addGlue() { /*...*/ }
    public Group addNode(Node node) { /*...*/ }
    public Group addGroup(Group g) { /*...*/ }
}
class SequentialGroup extends Group {}
class ParallelGroup extends Group {}
And here's the sample application:


The code is on github. It's a single .java file that you can easily drop in your application.

Comments

Popular posts from this blog

How to create your own simple 3D render engine in pure Java

Solving quadruple dependency injection problem in Angular

Better CLI option parsing in Scala