28 novembro 2008

Playing with layout: a custom JavaFX layout manager

Hi!

Did you looked at javafx.scene.Group class? In a first look, Group is only a simple collection of nodes. But, looking it more detailed, you will see that it has an important feature: a method to create layout managers! And JavaFX has two built in layouts: javafx.scene.layout.HBox and javafx.scene.layout.VBox.

The first one is a simple layout manager that puts all node in an horizontal line, one after other. And the second do the same, but in a vertical line. Try to use then, as you use Group.

But I need, for my application, a different layout manager: I need to put a collection of objects inside a box. This box has a limited size, and this object need to be arranged according to box size. So, I need an automatic grid layout, that defines the numbers of columns according to objects widths.

Some day ago, I've created this layout manager object, and only now it works fine. It works in horizontal or vertical orientation, and determines the number of columns/lines by objects sizes. I called it as AutoGridLayout, and it's code was here:

/*
* AutoGridLayout.fx
*
* Created on 12/11/2008, 17:26:00
*/

package brunogrossi.javafx.components;
import javafx.scene.*;

/**
* @author Bruno Grossi
*/

public class Alignment {
private attribute name: String;

/**
* Represents the central position.
*/
public static attribute CENTER = Alignment { name: "CENTER" }

/**
* Represents the begin position: LEFT when horizontal, TOP when vertical
*/
public static attribute BEGIN = Alignment { name: "BEGIN" }

/**
* Represents the end position: RIGTH when horizontal, BOTTOM when vertical
*/
public static attribute END = Alignment { name: "END" }

public function toString(): String { name }
}

public class AutoGridLayout extends Group {
/**The max width or height of this layout component*/
public attribute maxSize:Number on replace {
impl_requestLayout();
}

/**Indicates if it's horizontal(true) or vertical(false) layout*/
public attribute horizontal:Boolean=true on replace {
impl_requestLayout();
}

/**The spacing between elements*/
public attribute spacing:Number on replace {
impl_requestLayout();
}

/**The spacing between elements*/
public attribute alignment:Alignment=Alignment.CENTER on replace {
impl_requestLayout();
}

init {
impl_layout = doLayout;
}

private function doLayout(g:Group):Void {
if (sizeof this.content>0 and this.maxSize>0) {
var largerSize:Number = 0.0;

var size:Number;
for (node in this.content) {
if (node.visible) {
size = if (horizontal) node.getBoundsWidth() else node.getBoundsHeight();
if (size > largerSize)
largerSize = size;
}
}
var numberOfElements:Integer =
java.lang.Math.floor(this.maxSize / largerSize) as Integer;
size = this.maxSize / numberOfElements;
var maxOtherSize:Number=0.0;

var x:Number = 0;
var y:Number = 0;

for (node in this.content) {
if (node.visible) {
node.impl_layoutX = if (horizontal)
calcPosition(x, size, node.getBoundsWidth())
else x;
node.impl_layoutY = if (not horizontal)
calcPosition(y, size, node.getBoundsHeight())
else y;

if (indexof node mod numberOfElements == numberOfElements-1) {
if (horizontal) {
x=0;
y+=maxOtherSize+spacing;
maxOtherSize=0;
} else {
y=0;
x+=maxOtherSize+spacing;
maxOtherSize=0;
}
} else {
var otherSize;
if (horizontal) {
x += size + spacing;
otherSize = node.getBoundsHeight();
} else {//Vertical
otherSize = node.getBoundsWidth();
y += size + spacing;
}
if (otherSize>maxOtherSize)
maxOtherSize = otherSize;
}

}
}
}
}

private function calcPosition(pos:Number, size:Number, nodeSize:Number):Number {
if (this.alignment==Alignment.CENTER) {
var _center = pos + (size/2);
_center - (nodeSize/2);
} else if (this.alignment==Alignment.END) {
var _end = pos + size;
_end - nodeSize;
} else {//BEGIN is default option
pos;
}
}

}


Here is a demonstration code that uses the AutoGridLayout. You can test it with box of randon size or with same size, only changing "randomSize" value...

//Demo
function randSize():Number {
java.lang.Math.random()*50+50;
}

import javafx.scene.paint.Color;
var f:javafx.application.Frame = javafx.application.Frame {
var randomSize = true;
var colors = [Color.RED, Color.BLUE, Color.GREEN, Color.YELLOW, Color.BLACK]
visible:true
width:1000
height:1000
stage: javafx.application.Stage {
content: AutoGridLayout {
maxSize: 1000
horizontal:true
alignment: Alignment.CENTER
content:
for(i in [0..70])
javafx.scene.geometry.Rectangle{
width:if (randomSize) randSize() else 100
height:if (randomSize) randSize() else 100
fill:colors[i mod sizeof colors]
}
}
}
}


Here is an image of the demo, using random size boxes, in horizontal orientation. Notes that each line begins behind the other and each column has same size.

Um comentário:

Anonimus! disse...

for layout manager see DigLayout
http://code.google.com/p/diglayout/

-Diego-