mercredi 26 mai 2010

Distortion effect

After the simple scrolltexts and scrolltexts with a sinusoidal effect, here is just another old bitmap effect used in the demos written for home computers in the 1980s.



For standalone mode 
If you want to try with a bigger image, you can use the glassfish.jpg image instead of the fc-barcelone-logo.jpg and, for a better effect with a bigger image,  use this lines

var factor = (6 * Math.PI) / imageHeight;
    var v = (Math.sin(j * factor) * 20) + 20;
instead of this lines
var factor = (2 * Math.PI) / imageHeight;
    var v = (Math.sin(j * factor) * 10) + 20;
Distortion.fx
package distortion;

import javafx.scene.image.Image;
import javafx.geometry.Rectangle2D;
import javafx.scene.image.ImageView;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.util.Math;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.runtime.ConditionalFeature;
import javafx.runtime.Platform;
import javafx.scene.shape.Rectangle;
import javafx.scene.paint.Color;

/**
 * @author paddy
 */
println("Effect enabled: {Platform.isSupported(ConditionalFeature.EFFECT)}");
println("Input Method enabled: {Platform.isSupported(ConditionalFeature.INPUT_METHOD)}");
println("Scene 3D enabled: {Platform.isSupported(ConditionalFeature.SCENE3D)}");
println("Shape Clip enabled: {Platform.isSupported(ConditionalFeature.SHAPE_CLIP)}");

var img = Image {
            //url: "{__DIR__}images/glassfish.jpg"
            url: "{__DIR__}images/fc-barcelone-logo.jpg"
             }
def imageWidth = bind img.width as Integer;
def imageHeight = bind img.height as Integer;
def lineWidth = imageWidth;
def lineHeight = 1;
var distortionMap: Float[];
var index = 0;

for (j in [0..<imageHeight * 2]) {

    var factor = (2 * Math.PI) / imageHeight;
    var v = (Math.sin(j * factor) * 10) + 20;
    //var factor = (6 * Math.PI) / imageHeight;
    //var v = (Math.sin(j * factor) * 20) + 20;

    insert v into distortionMap;
}

def lineViewports = for (row in [0..imageHeight]) {
            Rectangle2D {
                minX: 0, minY: row, width: lineWidth, height: lineHeight
            }
        }
var distortionImg = bind
        for (row in [0..imageHeight]) {
            ImageView {
                x: bind distortionMap[row + index]
                y:  row
                viewport: bind lineViewports[row]
                image:  img
            }
        }
var t = Timeline {
            repeatCount: Timeline.INDEFINITE
            keyFrames: KeyFrame {
                time: 8ms
                canSkip: true
                action: function (): Void {
                    index = index + 1;
                    if (index > imageHeight) {
                        index = 0;
                    }
                }
            }
        }

t.play();

Stage {
    title: "Application title"
    scene: Scene {
        width: imageWidth +40
        height: imageHeight + 1

        content: [
            Rectangle {
                x: 0, y: 0
                width: imageWidth +40 , height: imageHeight + 1
                fill: Color.BLACK
                //fill: Color.GHOSTWHITE
            }
            distortionImg
        ]
    }
}

Note : The scrolltexts and the scrolltexts with sinusoidal effect work without migration on JavaFX 1.3 with AWT/Java2D/Swing toolkit.
On prism toolkit the animation is very fluid but you have to use a jpeg image instead of png for the font because the png is not supported in this early version of prism

samedi 8 mai 2010

JavaFX custom component in JavaFX 1.3

In JavaFX 1.2, Control extended CustomNode then we could override the create function to customize an existing component.  
With JavaFX 1.3, Control now extends
Parent directly then we do not have any more the create function and my custom component doesn’t compile any more … (because I used the create function to customize my components)

Then, to customize an existing component in JavaFX 1.3, I tried another way


I use the initBlock to create the nodes that I want to add to the existing component to customize it
and then I add these nodes to the children sequence of the existing component.

...
   init {
          var g = Group {
                    content: [
                                Group {
                                    ...
                                    content: [
                                        Circle {
                                               ...
                                        }
                                        Rectangle {
                                               ....
                                        }
                                     ...
                                    ]
                                    ...
                    ]
                }
           insert g into children;
        }
   ...
Now, I have a problem with the css, I want to have a default css for my SearchTextBox which is loaded by default by my component (not in the Scene by the developer) and which can be overridden by the developer.
If you have a solution, please let me know!
This example works on: Windows (Seven), Linux (Ubuntu 10.04) and Mac OS X (10.6.3)  with the Default toolkit and the Prism toolkit (JVM argument: -Xtoolkit prism) but there is one bug when you run it as an applet on Mac OS X.
The button have a wrong size, the text is too long….
[update August 8, 2010]this issue is fixed in JavaFX 1.3.1




SearchTextBox.fx
package customcomponent;

import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.input.MouseEvent;
import javafx.scene.control.TextBox;
import javafx.scene.input.KeyEvent;

/**
 * @author Patrick
 */
public class SearchTextBox extends TextBox {

    public var onResetSearch: function(): Void;
    public var onSearch: function(s: String): Void;

    override var style = "-fx-padding: 0 16 0 0; ";
    //override var styleClass = "searchTextBox text-box";
    //override var styleClass = "searchTextBox";

    override var onKeyTyped = function(event:KeyEvent){
        if (this.rawText!=""){
            onSearch(this.rawText);
        }else{
            onResetSearch();
        }
    }

    init {
          var g = Group {
                    content: [
                                Group {
                                    var crossWidth = bind this.layoutBounds.height * 0.45;
                                    var crossHeight = bind this.layoutBounds.height * 0.05;
                                    visible: bind this.rawText.length() > 0
                                    layoutX: bind this.layoutBounds.width - (this.layoutBounds.height / 2.0)
                                    layoutY: bind this.layoutBounds.height / 2.0
                                    content: [
                                        Circle {
                                            fill: Color.GRAY
                                            radius: bind this.layoutBounds.height * 0.325 // 65% of the texbox height and div 2 for the radius
                                        }
                                        Rectangle {
                                            width: bind crossWidth
                                            height: bind crossHeight
                                            translateX: bind 0 - crossWidth / 2.0
                                            translateY: bind 0 - crossHeight / 2.0
                                            fill: Color.WHITE
                                            rotate: 45
                                        }
                                        Rectangle {
                                            width: bind crossWidth
                                            height: bind crossHeight
                                            translateX: bind 0 - crossWidth / 2.0
                                            translateY: bind 0 - crossHeight / 2.0
                                            fill: Color.WHITE
                                            rotate: -45
                                        }
                                    ]
                                    onMouseClicked: function (event: MouseEvent) {
                                        this.commit();
                                        this.text = "";
                                        onResetSearch();
                                    }
                                }
                    ]
                }           
           insert g into children;
        }
}

Main.fx
package searchtextbox;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
import javafx.scene.control.Button;
import javafx.scene.control.RadioButton;

import javafx.scene.control.ToggleGroup;
import customcomponent.SearchTextBox;

/**
 * @author Patrick
 */

class ExtRadioButton extends RadioButton {
    public var action:function();

    override public var selected on replace {
        if (selected) {
            action();
        }
    }
}

def macStyleSheet="{__DIR__}resources/mac.css";
def defaultStyleSheet = "/customcomponent/stbcaspian.css";
var stylesheets:String = macStyleSheet;

Stage {
    title: "Application title"
    width: 250
    height: 200

    var cssToggleGroup = ToggleGroup {};

    scene: Scene {
        stylesheets : bind stylesheets
        content: [
            Text {
                font : Font {
                    size : 16
                }
                x: 10
                y: 30
                content: "Search "
            }
            SearchTextBox {
                    styleClass : "searchTextBox"
                    translateX :10
                    translateY :40


                    onResetSearch:function(){
                        println("reset !");
                    }
                    onSearch:function(s: String){
                        println("Search of : {s}");
                    }
            }
            Button {
                    text: "just a button to change focus"
                    translateX :10
                    translateY :80
                    action: function() {
                        println("Hello !")
                   }
            }

            ExtRadioButton {
                            text: "use the default CSS"
                            translateX :10
                            translateY :110
                            toggleGroup: cssToggleGroup

                            action: function() {
                                stylesheets = defaultStyleSheet;
                            }
            }

            ExtRadioButton {
                            text: "use the mac CSS"
                            translateX :10
                            translateY :130
                            toggleGroup: cssToggleGroup
                            selected: true

                            action: function() {
                                stylesheets = macStyleSheet;
                            }
            }
        ]
    }
}