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

Post a Comment

[フレーム]

Popular posts from this blog

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

3D render engines that are nowdays used in games and multimedia production are breathtaking in complexity of mathematics and programming used. Results they produce are correspondingly stunning. Many developers may think that building even the simplest 3D application from scratch requires inhuman knowledge and effort, but thankfully that isn't always the case. Here I'd like to share with you how you can build your very own 3D render engine, fully capable of producing nice-looking 3D images. Why would you want to build a 3D engine? At the very least, it will really help understanding how real modern engines do their black magic. Also it is sometimes useful to add 3D rendering capabilities to your application without calling to huge external dependencies. In case of Java, that means that you can build 3D viewer app with zero dependencies (apart from Java...

Solving quadruple dependency injection problem in Angular

Angular comes with built-in dependency injection and module management system, but it can get a bit tedious to use when your project grows. Especially if you use RequireJS alongside it (a must for almost any project) and also want to be minification-safe with all those dependency injections. Let's look at a simple example with one module that provides a value and another module that prints that value: // answer.js define(["angular"], function(angular) { angular.module("the.answer") .value("TheAnswer", 42); }); // main.js define(["angular", "the.answer" ], function(angular) { angular.module("main", [ "the.answer" ]) .run([ "TheAnswer" , function( TheAnswer ) { console.log(TheAnswer); }]); }); As you can see, to provide a single dependency to my code, I had to specify it in four places (highlighted in red) - first as RequireJS dependency, then as Angular module dependency,...

Configuration objects in Scallop

It turned out that spreading type annotations throughout all the code in all places where options are requested is not a good idea. And, of course, it is prone to errors - who likes those type annotations? When using such option parsing libraries, users usually end up creating a big object with fields corresponding to each option: object Conf { var apples:Int = 0 var bananas:Int = 0 } // ... parsing options ... Conf.apples = parser.getOption("option name", ...) Conf.bananas = parser.getOption("other option name", ...) Sure, this works, and usually flexible enough - but why would anybody want to create such boilerplate? And by the way, nobody likes var's in their code :) So in Scallop , I needed to cut down that unnecessary repetition. There are several ways: Create a compiler plugin. The most powerful option. And the least enjoyable - even given the ease of installing a compiler plugin using sbt, I doubt that anybody will install compiler p...