May 1 2010

JavaFX: How to extend CustomNode properly

I have taken a look at custom controls before. Now it is time to examine how to extend CustomNodes.

In this post I take the same steps as before.

Empty CustomNode

Since 1.3 it is no longer necessary to implement any methods. So it is possible to create and use an empty class.
As expected the layoutBounds and boundsInLocal both have a width/height of 0.

CustomNode with a Rectangle

We add a Rectangle to our custom node. In the docs there are two possible ways described.

Overriding children var

The new, recommended way is to override the children var.
This way works as expected. The layoutBounds and boundsInLocal correspond to the bounds of the rectangle.

Overriding create()

This has been the old way to add children to a CustomNode. We just return the same rectangle.
And it works as expected.
(By the way: The default implementation of create() returns null).

Assigning children in init()/postinit()

But when there is a children var, why not simply assigning the children within the init method? So let’s try: Yes – works as it should. Bounds are calculated correctly. So I can’t tell you why the doc does not mention that way….

Adding two rectangles

No we add two rectangles as children that have different sizes and locations. The bounds contain both rectangles. This fact approves that a CustomNode has the same behavior as a Group.

Adding effects

Adding an effect to the rectangle

Adding an effect (e.g. DropShadow) to the Rectangle changes both bounds. So CustomNode has the same behavior as a Group.

Adding an effect to the CustomNode itself

Adding an effect (e.g. DropShadow) to the CustomNode itself only changes the boundsInLocal. The layoutBounds only depend on the children. While this is the expected (and documented) behavior, it is good to see that it has been implemented correctly…


Apr 28 2010

New JavaFX default font (Amble Condensed) looks ugly!

Updates: Screenshots created on Mac/Windows added.

With the 1.3 relase of JavaFX there has been introduced a new default font: Amble Condensed.

The default font in 1.2 has been Dialog 12pt.
But that decision has some disadvantages – at least on Linux…. But one screen shot tells more than a thousand words:
fx-fonts

You can easily spot the difference…

When using Dialog a font size of 9 can easily be used. Maybe 8 if really necessary…  The smallest usable size with Amble seems to be 12 (that is the default) – maybe, if really necessary 11…

But even the default size of 12 is very problematic. I didn’t want to read long texts drawn in Amble 12…

Is there anybody that could run those tests on Windows and post a screen shot?
Windows Screenshot can be found down there…

Code for my demo:
[cc lang="c"]
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
import javafx.scene.layout.VBox;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.layout.Stack;
import javafx.scene.shape.Rectangle;
import javafx.scene.Node;

def colors = [ Color.BLACK, Color.WHITE ];

Stage {
title: “Application title”
scene: Scene {
width: 800
height: 600
content: [
VBox {
var stack:Stack;

content: [
for ( color in colors ) {
stack = Stack {
content: [
Rectangle{
width: bind stack.width
height: bind stack.height
fill:color
}

HBox {
content: [
for ( font in [ Font.DEFAULT.name, "Dialog" ] ) {
VBox {
content: [
for ( size in [ 6..20 ] ) {
Text {
fill: colors[(indexof color +1) mod colors.size()]
font: Font {
size: size
name: font
}
content: “{font} with size { size }”
}
}
]
}

}
]
}
]
}
}
]
}
]
}
}

println( “Default font: {Font.DEFAULT}” );
[/cc]

UPDATE:
John Tonkin send be a screen shot made using Windows:

default-font-windows

It doesn’t look any better…

And here is a shot created on a Mac by Antoine Mischler. Does it look different?
mac

Conclusion

The screenshots created on Linux and Windows look nearly the same (for “Amble Condensed”). The one created on the Mac looks a little bit better (look at 11pt)…
But be careful! The bounds are a different on Mac than on Windows/Linux (e.g. at 16pt)…


Apr 27 2010

JavaFX Light Bulb with improved UI performance

Update: JNLP file has been fixed. Should work now (me == stupid)…

I am sure most of you have seen the nice demo posted by Mark Anro Silva.

While this is a very nice demo it has a performance problem related to the slider that is very common in JavaFX.
The effectOpacity of the LightBulbEffect is bound to the value of the slider. While this is the correct logic, we run into problems, if those values are updated too often.
A slider is able to create several updates/events every second. When expensive effects are calculated every time, we will get a UI that is not very responsive (and feels slow).

Therefore it is a good idea to not to bind expensive actions to UI triggers directly. Instead you could use a timeline to delay the action for some milliseconds.

For that purpose I have created a DelayedAction.

[cc lang="c" lines="5" tab_size="2" lines="20"]
package com.cedarsoft.fx;

import javafx.animation.Timeline;
import javafx.animation.KeyFrame;

/**
* A DeplayedAction.
* This class can be used to delay actions. This is especially useful if used with sliders or other UI components
* that update its values very fast.
* When binding long running / processor intensive actions to those variables your application will start to feel
* unresponsive.

*
* If maxDelay is set, the action is executed at least {@link #maxDelay} after the call of {@link #schedule()}.
*/
public class DelayedAction {

/**
* The action that is called after the delay has passed.
*/
public-init var action: function(): Void;
/**
* The delay
*/
public-init var delay: Duration = 50ms;
/**
* The maximum deplay the action is called after.
* If the max delay
*/
public-init var maxDelay: Duration;
public-read def maxDelaySet = bind maxDelay > 0ms on replace {
if ( not maxDelaySet ) {
maxDeplayTimeLine.stop();
}
};
/**
* if set to true every call to {@link #schedule} will executed immediately.
* Pending requests will be executed immediately.
*/
public var executeImmediately: Boolean on replace {
if ( executeImmediately and timeline.running or maxDeplayTimeLine.running ) {
execute();
}
};
def timeline: Timeline = Timeline {
keyFrames: [
KeyFrame {
time: bind delay
action: function () {
execute();
}
}
]
}
def maxDeplayTimeLine: Timeline = Timeline {
keyFrames: [
KeyFrame {
override var time= bind maxDelay on replace {
maxDeplayTimeLine.evaluateKeyValues();
}
action: function () {
execute();
}
}
]
}

/**
* The action is called after the given delay. Every call to schedule restarts the delay.
*
*/
public function schedule() {
if ( executeImmediately ) {
execute();
return ;
}
if ( maxDelaySet ) {
maxDeplayTimeLine.play();
}
timeline.playFromStart();
}

/**
* Executes the action immediately.
*/
public function execute(): Void {
maxDeplayTimeLine.stop();
timeline.stop();
action();
}
}
[/cc]

That action is part of the fx.commons-library (snapshot can be downloaded from
http://nexus.cedarsoft.com/content/groups/public-snapshots/com/cedarsoft/fx/commons/1.0.0-SNAPSHOT/).

How to use DelayedAction

The usage of the DelayedAction is quite simple. Instead of directly binding the variable, you assign the var within a delayed action.

[cc lang="c"]
def lightOpacitySetAction: DelayedAction = DelayedAction {
delay: 50ms
maxDelay: 500ms
executeImmediately: true //this is only necessary for this demo. Will be changed to false as soon as the slider becomes visible
action: function () {
println( “—> SETTING NEW VALUE! {slider.value}” );
lightOpacity2 = slider.value;
}
};
[/cc]

That action is triggered if the value of the slider is changed. Therefore we introduce a temporary var.

[cc lang="c"]var tmp = bind slider.value on replace{
lightOpacitySetAction.schedule();
}
[/cc]

Of course it is necessary to remove the binding of lightOpacity2 to slider.value.

Updated Demo

I have updated the demo application accordingly:

[cc lang="c"]
package com.cedarsoft.fx;

import javafx.scene.shape.QuadCurveTo;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.image.ImageView;
import javafx.scene.Group;
import javafx.scene.shape.Rectangle;
import javafx.scene.paint.Color;
import javafx.scene.effect.GaussianBlur;
import javafx.scene.effect.Glow;
import javafx.scene.shape.Path;
import javafx.scene.control.Button;
import javafx.scene.input.MouseEvent;
import javafx.scene.control.Slider;
import javafx.scene.control.Label;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.CustomNode;
import javafx.animation.Timeline;
import javafx.animation.KeyFrame;
import javafx.animation.Interpolator;
import java.lang.UnsupportedOperationException;

function run(_ARGS:String[ ]){
var buttonstext =”On” on replace {
if ( buttonstext == “Off” ) {
lightOpacity1 = .1;
lightOpacity3 = .3;
lightOpacity4 = 1;
controlVisible = true;
slider.value = .3;
lightOpacitySetAction.executeImmediately = false;
} else {
lightOpacity1 = 0;
lightOpacity3 = 0;
lightOpacity4 = 0;
controlVisible = false;
lightOpacitySetAction.executeImmediately = true;
slider.value = 0;
}
};

var lightOpacity1: Number = 0;
var lightOpacity2: Number = 0;
var lightOpacity3: Number = 0;
var lightOpacity4: Number = 0;
var lightbulbImg = ImageView {
image: Image {
url: “{__DIR__}resources/lightbulb.jpg”
}
}

def lightOpacitySetAction: DelayedAction = DelayedAction {
delay: 50ms
maxDelay: 500ms
executeImmediately: true
action: function () {
println( “—> SETTING NEW VALUE! {slider.value}” );
lightOpacity2 = slider.value;
}
};

var controlVisible = false;
var effects = Group {
content: [
Rectangle {
width: 412
height: 520
fill: Color.YELLOW
opacity: bind lightOpacity1
}
LightBulbEffect { effectOpacity: bind lightOpacity2 }
LightBulbEffect { effectOpacity: bind lightOpacity3 }
Rectangle {
effect: GaussianBlur {
radius: 10
input: Glow { }
}
translateY: 178
translateX: 165
width: 65
height: 8
arcWidth: 8
arcHeight: 8
fill: Color.WHITE
stroke: Color.ORANGE
strokeWidth: 1.8
opacity: bind lightOpacity4
}
Path {
effect: GaussianBlur {
radius: 1
input: Glow { }
}
stroke: Color.WHITE
strokeWidth: 1.8
opacity: bind lightOpacity4
elements: [
MoveTo { x: 172 y: 183 },
LineTo { x: 183 y: 182 },
QuadCurveTo {
controlX: 198 controlY: 183
x: 213 y: 182
}
QuadCurveTo {
controlX: 214 controlY: 183
x: 225 y: 185
}
]
}
]
}
var togglebutton = Button {
translateY: 350
translateX: 260
text: bind buttonstext
onMousePressed: function ( ev: MouseEvent ): Void {
if ( buttonstext == “Off” ) {
buttonstext = “On”;
} else {
buttonstext = “Off”;
}
}
}
var slider: Slider = Slider {
translateY: 420
translateX: 128
max: .4
min: 0
visible: bind controlVisible
}

var tmp = bind slider.value on replace{
lightOpacitySetAction.schedule();
}

var controls = Group {
content: [
togglebutton,
slider,
Label { translateX: 120 translateY: 419 text: "-" visible: bind controlVisible }
Label { translateX: 268 translateY: 419 text: "+" visible: bind controlVisible }
]
}

Stage {
title: “JavaFX Light Bulb”
scene: Scene {
width: 402
height: 499
fill: Color.BLACK
content: [
lightbulbImg,
effects,
controls
]
}
}
}

public class LightBulbEffect extends CustomNode {

public var effectOpacity: Number;

override function create() {
Path {
translateX: -10
translateY: -8
effect: GaussianBlur {input: Glow {level: 1} radius: 63}
fill: Color.YELLOW
stroke: Color.YELLOW
strokeWidth: 5
opacity: bind effectOpacity
elements: [
MoveTo {x: 127 y: 132},
QuadCurveTo {
controlX: 206 controlY: 48
x: 287 y: 132
}
QuadCurveTo {
controlX: 320 controlY: 180
x: 295 y: 230
}
QuadCurveTo {
controlX: 290 controlY: 240
x: 270 y: 280
}
QuadCurveTo {
controlX: 270 controlY: 295
x: 260 y: 320
}
LineTo {x: 153 y: 320}
QuadCurveTo {
controlX: 143 controlY: 295
x: 143 y: 280
}
QuadCurveTo {
controlX: 118 controlY: 240
x: 115 y: 230
}
QuadCurveTo {
controlX: 95 controlY: 180
x: 127 y: 132
}
]
}
}
}[/cc]


Apr 27 2010

JavaFX: Transparency and Linux

There have been a few posts regarding transparency under Linux. I have put that code together so that it can be used directly. This works for me:

[cc lang="c"]
public def osName = FX.getProperty( “javafx.os.name” );
public def IS_LINUX = osName.contains( “inux” );
public def IS_MAC = osName.contains( “mac” );
public def IS_WINDOWS = osName.contains( “indows” );
public def IS_SOLARIS = osName.contains( “olaris” );

/**
* Is the version of the running JDK at least major.minor.micro_update?
* In 1.6.0_18 macro=1,minor=6,micro=0,update=18
*/
public function jdkAtLeast( macro: Integer, minor: Integer, micro: Integer, update: Integer ): Boolean {
def runtimeVersion = java.lang.System.getProperty( “java.runtime.version” );
def pattern = java.util.regex.Pattern.compile( “^(\\d)\\.(\\d)\\.(\\d)_(\\d+)-” );
def matcher = pattern.matcher( runtimeVersion );
if ( matcher.find() ) {
def currentMacro = Integer.valueOf( matcher.group( 1 ) );
def currentMinor = Integer.valueOf( matcher.group( 2 ) );
def currentMicro = Integer.valueOf( matcher.group( 3 ) );
def currentUpdate = Integer.valueOf( matcher.group( 4 ) );
if ( currentMacro < macro or currentMinor < minor or currentMicro < micro or currentUpdate < update ) {
return false;
}
}
true
}

public function fixTransparency() {
if ( IS_LINUX and jdkAtLeast( 1, 6, 0, 14 ) ) {
java.lang.System.setProperty( “javafx.allowTransparentStage”, “true” );
}
}[/cc]


Apr 27 2010

JavaFX 1.3: Template for custom controls

Here is a small template that helps you to create your own custom control without any problem:

Just paste this code into your fx file and rename the classes using the IDE you prefer…

[cc lang="c"]
public class MyOwnControl extends Control {
init {
if ( skin == null ) {
skin = MyOwnControlSkin {};
}
}
}

public class MyOwnControlSkin extends Skin {
def myBehavior = bind behavior as MyOwnBehaviour;
def myControl = bind control as MyOwnControl;

init {
behavior = MyOwnBehaviour{};

node = Rectangle {
width: bind control.width
height: bind control.height
}
}

override function getPrefWidth( number ) {
//return your pref width – hard coded values are ok for most cases
}

override function getPrefHeight( number ) {
//return your pref height – hard coded values are ok for most cases
}

/*
//It is not absolutly necessary to override those methods, but strongly recommended.
override function getMaxWidth() {
//Return the max width
}

override function getMaxHeight() {
//Return the max height
}

override function getMinWidth() {
//Return the min width
}

override function getMinHeight() {
//Return the min height
}*/

override function contains( localX: Number, localY: Number ): Boolean {
node.contains( localX, localY );
}

override function intersects( localX: Number, localY: Number, localWidth: Number, localHeight: Number ): Boolean {
node.intersects( localX, localY, localWidth, localHeight );
}
}

public class MyOwnBehaviour extends Behavior {
def myControl = bind skin.control as MyOwnControl;
}
[/cc]


Apr 23 2010

JavaFX: Custom controls in 1.3

The 1.3 release of JavaFX is a huge on. Many things have been improved. Also some incompatible API changes occured.

Creating Custom controls has been really hard in 1.2. I have created a template that did what it should. But with the 1.3 release this template does no longer work.

So here we go and look at the issues related to custom controls one more time…. It is the same setup: The custom control is placed within a Stack.

Empty control

We start with an empty control without a skin. The result is the same as in 1.2: Everything is 0.

Simple Control with and empty Skin

Now we create a control and assign an empty skin. This is done within the init-block since createNode no longer exists. This results in the same behavior as in 1.2: There are hard coded values for prefWidth/height that are queried by the stack, set as width/height of the custom control and reflected by the layoutBounds. The boundsInLocal are still (0/0/0/0).

The Stack implementation has been changed. In 1.2 the Stack resized all nodes that implement Resizable to fill the available space. The default behaviour in 1.3 is to use the prefWidth/height.
The old behavior can be achieved by overwriting getHFill()/getVFill.

Skin containing just a Rectangle

Skin with RectangleThe skin returns a Rectangle {widht:70 height:150}. The layoutBounds are not affected in any way. But the boundsInLocal are bound to the bounds of the node.

The result looks different than in 1.2: But just because the control is not resized! As a result the rectangle is drawn somewhere near the middle (but not in the middle). The position of the upper left border of the rectangle is placed by the Stack using the layoutBounds (drawn in red) as initial value for calculation.

So the result might be a little bit “dangerous”: For skins that have boundsInLocal that are near the hard coded values for prefWidth/height (and therefore width/height and therefore layoutBounds) the layout might be “good enough”. So the missing connection between width/height and boundsInLocal could be overlooked…

Skin with prefWidth/Height methods

The implementation of the prefWidth/Height methods is crucial – as written before. It has the expected results: The width/height of the control (and as result the layoutBounds) are changed accordingly by the container.

Connecting the width/height of the rectangle

As written before it is necessary to bind the width/height of the rectangle to the width/height of the control. When this is done the result looks as expected.

Conclusion

Creating custom controls in JavaFX is still quite difficult. And the documentation is still very poor. The template has to be changed slightly (due the removal of the create method). I will do this after the weekend.

The default of the Stack has been changed (and I like that). So it uses the prefWidth/Height per default. While this is the better behavior (imho) it might hide the fact that you have forgotten to implement the getPrefWidth/Height methods.

Please Oracle: Make those methods abstract asap!


Apr 23 2010

JavaFX 1.3 is available…

As (propably) everybody knows, since yesterday JavaFX 1.3 is available.

And I don’t like that new kind of release-date-will-be-a-surprise stuff. Google does it with Android. Oracle with JavaFX… At the same time the code is kept secretly.

I prefer an open source stack. Just to be independent if some big company uses its petty cash to buy some stuff… I have heard this has happened recently…


Apr 19 2010

JavaFX: Custom controls template

Update:
This template is made for JavaFX 1.2! For 1.3 use the template posted here instead.

Here is a small template that helps you to create your own custom control without any problem:

[cc lang="c"]
public class MyControl extends Control {
override function create(): Node {
if ( skin == null ){
skin = MySkin{};
}
super.create();
}
}

public class MySkin extends Skin {
def myBehavior = bind behavior as MyOwnBehaviour;
def myControl = bind control as MyOwnControl;

init {
node = Rectangle {
width: bind control.width
height: bind control.height
}
behavior = MyOwnBehaviour{};
}

override function getPrefWidth( number ) {
//return your pref width – hard coded values are ok for most cases
}

override function getPrefHeight( number ) {
//return your pref height – hard coded values are ok for most cases
}

/*
//It is not absolutly necessary to override those methods, but strongly recommended.
override function getMinWidth() {
}

override function getMinHeight() {
}

override function getMaxWidth() {
}

override function getMaxHeight() {
}
*/

override function contains( localX: Number, localY: Number ): Boolean {
node.contains( localX, localY );
}

override function intersects( localX: Number, localY: Number, localWidth: Number, localHeight: Number ): Boolean {
node.intersects( localX, localY, localWidth, localHeight );
}
}

public class MyOwnBehaviour extends Behavior {
def myControl = bind skin.control as MyOwnControl;
}
[/cc]


Apr 19 2010

JavaFX: Creating custom controls – the right way

Is is quite difficult to create custom controls in JavaFX that perform like they should. This is the result of lack of documentation, unavailability of source code and a not-so-good API…

I struggled with that issue for two days. And finally I think I understood the hole concept. I wrote about that process in this blog some days ago.

If you are just looking for a template, continue here.

Here is a small drawing that visualizes the relevant methods/fields related to custom controls:
control2skin

width/height

The width and height are set by the container. The container calculates the width/height based on the values returned by getPrefWidth/Height and getMaxWidth/Height.

layoutBounds

The layoutBounds are calculated automatically based on the values of width/height of the control.

Attention: The layoutBounds do not depend on the layoutBounds of the node created by the Skin!

This might be counter-intuitive. But it is necessary since the Control is resizable. And therefore the layoutBounds have to depend on the width/size (which are set by the container).

boundsInLocal

The boundsInLocal are bound to the boundsInLocal of the node created by the skin. Just as you might have expected.

getMin/Pref/MaxWidth/height

The control delegates those method calls to the currently active Skin. At least it is necessary to implement the getPrefWidth/Height methods. The default implementation just returns hard coded values (100/50). If you don’t have implemented those methods, the layoutBounds of the control will be wrong. And this will result in odd layout behaviour of your custom node!

Which methods to extend? Which fields to bind?

There are two things you have to do:

Step 1: Override getPrefWidth/Height in Skin

This is *really* necessary. Those methods should have been made abstract. I can’t imagine any case where those methods don’t have to be overridden… So just override them. Returning hard coded values is ok for most cases.

Step 2: Bind the size of the node to Control#width/height

Whenever the layoutBounds of the control change, the size of the node must be adjusted. It depends on your implementation how the relationship might be. For simple nodes without any effects a simple bind should be enough:

[cc lang="c" escaped="true" tab_size="2" lines="20"]
class MySkin extends Skin {
init {
node = Rectangle {
width: bind control.width
height: bind control.height
}
}

[/cc]

Now you have created a custom Control that behaves as expected – and is resizable…

I have created a template to create custom a control and skin.


Apr 19 2010

JavaFX: Bounds by example

There are several misunderstandings related to bounds. (Amy Fowler tried to explain the layout mechanisms some days ago)

There are three bounds that can be used:

  • layoutBounds – represent the *logical* bounds used to layout the node
  • boundsInLocal – represent the *real* bounds used to paint the node
  • boundsInParent – the boundsInLocal transformed to the coordinates of the parent.

And now there is simple stuff you should remember:

layoutBounds are used for layouting (only). They do not have anything to do with boundsInLocal. They are just a logical concept. Sometimes they are the same as the boundsInLocal. Sometimes they are smaller, sometimes larger.

Some examples

layoutBounds == boundsInLocal

This is the case for many very simple nodes. For example a rectangle without any effects like drop shadows.

layoutBounds < boundsInLocal

This is the case for nodes that have some type of effect – for example a drop shadow. That shadow must not be included into the layout. If the layout algorithm used the boundsInLocal (including the drop shadow) the nodes would look misaligned to the human eye.

Aligned using the layoutBounds:

layoutBounds used

Aligned using the boundsInLocal:

sample-unaligned

layoutBounds > boundsInLocal

This is the case if some parts of the node are (yet) invisible, but the human eye interpolates them:

PacMan with bounds

If those Pacman like circles are aligned using the boundsInLocal this will result in: Pacmans aligned with boundsInLocal

While the human eye expects the alignment to be more like that: Pacmans aligned by layoutBounds

Disclamier:

I know that this post does not contain many (if any) new informations. It is just a little bit shorter. And therefore might be helpful for somebody.