Retour à JavaFX

Dans un post précédent je disais que ce qui manquait à JavaFX était un outil de conception graphique pour permettre une collaboration graphiste/développeur selon moi indispensable à l’adhésion de la communauté à cette technologie. Sachant que les graphistes ne changeront pas d’outils facilement la meilleure approche serait de faire un pont entre les outils de design existants et JavaFX. C’est l’objectif de Project NILE en proposant un plugin Illustrator et un plugin Photoshop permettant d’exporter un FXD ou un fichier FX mais aussi un convertisseur SVG et un visualiseur graphique.
C’est selon moi la bonne stratégie, je me suis donc lancé dans un test.

Pour bien mettre en valeur la collaboration graphiste/développeur je suis allé chercher des travaux sur le net, j’ai trouvé des boutons « glossy » proposé en .ai et en .svg.
alphabet_glass_bars
Le test SVG n’est pas concluant, la transformation ne reprend pas les effets et pas mal de corrections sont nécessaires pour arriver à compiler. Je bascule de suite sur une conversion via Illustrator. Le premier coup ne compile pas car je suis européen ;). Au deuxième coup ça compile et le résultat est quasi identique au document .ai, juste un léger problème de couleur rapidement corrigé.
Le script FX est composé d’un ensemble de SVGPath, chaque caractère du clavier est aussi défini en un SVGPath. Un peu laborieux à exploiter tel quel il serait plus pratique d’extraire un composant et d’y appliquer des comportements comme typiquement un clic souris et en lui passant en paramètre sa position et le caractère à afficher.
J’extrais donc du script la définition d’un bouton Je remplace le SVGPath du caractère par un composant Text. J’ajoute un évènement sur le clic souris et je modifie l’aspect quand le bouton de la souris est enfoncé.

GlassButton.fx

package testfx1;

import javafx.scene.*;
import javafx.scene.geometry.*;
import javafx.scene.image.*;
import javafx.scene.paint.*;
import javafx.scene.text.*;
import javafx.scene.effect.*;
import javafx.input.MouseEvent;
import javafx.scene.transform.*; 
public class GlassButton extends CustomNode {
    public attribute letter:String;
    public attribute onClick:function(t:String):Void;
    public attribute x:Integer;
    public attribute y:Integer;
    protected function create() : Node {
        var effect = DropShadow { offsetX:0.0,offsetY:1.0,color:Color.BLACK,radius:8.0};
        return Group {
            translateX: x
            translateY: y
            content: [
                SVGPath {
                    effect: bind effect
                    fill: LinearGradient{proportional: false startX: 21.00 startY:35.00 endX: 21.00 endY: 5.00 stops: [
                            Stop {offset: 0.000 color: Color.rgb(0xd2,0xd2,0xd2,1.0)},
                            Stop {offset: 0.500 color: Color.rgb(0x4e,0x4e,0x4e,1.0)},
                            Stop {offset: 1.000 color: Color.BLACK},
                        ]}
                    content: "M36.00,27.00 C36.00,31.42 32.42,35.00 28.00,35.00 L14.00,35.00 C9.58,35.00 6.00,31.42 6.00,27.00 L6.00,13.00 C6.00,8.59 9.58,5.00 14.00,5.00 L28.00,5.00 C32.42,5.00 36.00,8.59 36.00,13.00 Z "
                },
                SVGPath {
                    fill: Color.GRAY
                    content: "M34.00,22.77 L8.00,22.77 L8.00,14.00 C8.00,9.86 11.55,6.50 15.92,6.50 L26.08,6.50 C30.46,6.50 34.00,9.86 34.00,14.00 Z "
                },
                Text {
                    fill: Color.WHITE
                    font: Font { 
                        size: 18 
                        style: FontStyle.BOLD
                    }
                    x: 14, y: 27
                    content : letter
                }
            ]
            onMouseClicked: function( e: MouseEvent ):Void {
                onClick(letter);
            }
            onMousePressed: function( e: MouseEvent ):Void {
                effect = null;
            }
            onMouseReleased: function( e: MouseEvent ):Void {
                effect = DropShadow { offsetX:0.0,offsetY:1.0,color:Color.BLACK,radius:8.0};
            }
        };
    }
}

Je crée alors une deuxième classe qui va gérer la disposition des touches en bouclant sur un tableau de caractères.

Keyboard.fx

package testfx1;
import javafx.scene.*;
import javafx.scene.text.*;
import javafx.scene.paint.*;

public class Keyboard extends CustomNode {
    public function create():Node {
        java.lang.System.out.println("BuildLetters …");
        var t:String;
        var letters = [
            "A", "B", "C", "D", "E", "F", "G", "H", "I",
            "J", "K", "L", "M", "N", "O", "P", "Q", "R",
            "S", "T", "U", "V", "W", "X", "Y", "Z", "@"
        ];
        var buttons:Node[] = [
            Text {
                font: Font { 
                    size: 14 
                    style: FontStyle.PLAIN
                }
                x: 5, y: 16
                content: bind t;
            
            }
        ];
        var posX:Integer = 0;
        var posY:Integer = 0;
        var incI:Integer = 0;
        var incJ:Integer = 0;
        for (i in letters) {
        if (
            incI mod 9 == 0) {
                posY = 35 * (incJ ++ ); 
                posX = 0;
                incI = 0;
            } else {
                posX = 35 * incI;
            }
        insert GlassButton {
                x: posX
                y: bind 45 + posY
                letter : i
                onClick : function(s:String) {
                    t += s;
                }
            } into buttons;
        incI++;
        }
        java.lang.System.out.println("OK.");
        return Group {
            content : buttons
        }
    }
}

Et puis j’insère le tout dans un Applet en suivant les conseils de James Weaver

GlassButtonApplet.fx

package testfx1;
import javafx.application.*;
import javafx.scene.*;
import javafx.scene.text.*;
import javafx.scene.paint.*;

Application {
    stage: Stage {
        content: Keyboard {}
    }
}

Ci-dessous le résultat, cliquez sur les touches et pour le sortir du navigateur vous enfoncez la touche alt et déplacez l’Applet. Le temps de génération est long (30s) soyez patient, je ne comprends pas pourquoi, je vais demander à Sun.

Note : le fait de pouvoir sortir l’Applet du browser oblige à créer un JNLP et donc de signer tous les jars dépendant de l’Applet. Un peu laborieux il faut penser à automatiser cette tâche :

  1. création du certificat : keytool -genkey [-keystore <cheminDuKeystore>] -alias <nomDuCertificat>
  2. signature de jat : jarsigner [-keystore <cheminDuKeystore>] <monFichier>.jar <nomDuCertificat>

Bilan
En mettant bout à bout le temps passé en développement pur on peut compter environ 4h, incluant la prise en main de JavaFX. C’est correct mais il serait bien de pouvoir réduire encore ce temps. Il est selon moi difficile de manipuler du JavaFX sans au moins un visualiseur temps réel digne de ce nom. Celui fournit dans NetBeans 6.1 est un peu lent. Enfin l’idée est là et elle est prometteuse car avec le Java Plugin à 4Mo on pourra enfin avoir des applis riches full Java qui ont l’avantage d’être en mesure de faire plus que la concurrence : accès aux périphériques serie, Bluetooth, … du poste client, utilisation d’API Java existantes (ex : JMS), bascule en mode desktop, …