Visualizzazione post con etichetta ui. Mostra tutti i post
Visualizzazione post con etichetta ui. Mostra tutti i post

Interattività per elementi GUI

Ultima lezione, la numero 8, del corso introduttivo SDN (Sun Developer Network) sulla costruzione di applicazioni GUI con JavaFX.

Per gestire l'interazione con l'utente, JavaFX usa un meccanismo di gestione degli eventi, dove ogni oggetto che può essere di interfaccia ha variabili locali che mappano funzioni che hanno a che fare con eventi.

Common Profile

Lo script che scriviamo visualizzerà un bottone a due stati (Play-Pause) che potrà essere mosso per la scena.

Quattro immagini per un bottone sulla scena

Scarichiamo dal sito sun le quattro immagini che ci serviranno per visualizzare il nostro bottone. Il play normale e premuto, lo stop normale e premuto, e mettiamole nel folder del nostro progetto.

Quindi iniziamo a scrivere il nostro script fx. Importiamo le classi per il nostro stage e la scena, poi consideriamo che avremo un gruppo come contenuto della scena, e infine le classi per l'immagine e la sua vista.
Carichiamo le nostre quattro immagini in altrettante costanti, e assegnamo una di queste alla variabile image.
Creiamo una vista per un immagine, che sarà legata alla variabile immagine appena definita. Il trucco, evidentemente, sarà quello di sfruttare il binding tra queste due variabili per gestire il cambiamento di stato del bottone.

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.Group;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

def playNormal = Image { url: "{__DIR__}play_onMouseExited.png" };
def playPressed = Image { url: "{__DIR__}play_onMousePressed.png" };
def stopNormal = Image { url: "{__DIR__}stop_onMouseExited.png" };
def stopPressed = Image { url: "{__DIR__}stop_onMousePressed.png" };

var image = playNormal;
var button = ImageView {image: bind image}

Stage {
title: "Play Button"
scene: Scene {
width: 300
height: 240
content: Group {
content: button
}
}
}

Gestione degli eventi

Vogliamo gestire tre eventi del mouse: la sua pressione, il suo rilascio e il dragging.

Definiamo quindi, prima del nostro stage, tre variabili per tener traccia dello stato del bottone e della sua posizione, in caso lo si trascini per la scena.

var mode = true; //true for Play, false for Stop
var X: Number;
var Y: Number;

Assegnamo ora al Gruppo che é il content della scena i valori relativi alla gestione degli eventi del mouse.

Gestire la pressione del mouse

Quando il mouse viene premuto (onMousePressed) teniamo traccia delle coordinate correnti del bottone e cambiamo l'immagine visualizzata.

onMousePressed: function(event) {
X = event.sceneX - event.node.translateX;
Y = event.sceneY - event.node.translateY;
image = if (mode) { playPressed; } else { stopPressed; };
}

Gestire il rilascio del mouse

Quando rilasciamo il tasto del mouse (onMouseReleased) dobbiamo cambiare l'immagine del bottone e cambiarne lo stato:

onMouseReleased: function(event) {
image = if(mode) stopNormal else playNormal;
mode = not mode;
}

Gestire il trascinamento del mouse

In questo caso non cambiamo l'immagine del bottone, inoltre il movimento é limitato dai limiti stabiliti della scena, per cui controlliamo se l'offset risultante sia esterno alla scena (minore di zero o tale che il bottone sia anche solo parzialmente esterno all'altro lato), nel qual caso forziamo il valore al limite accetabile:

onMouseDragged: function(event) {
def offsetX = event.sceneX - X;
event.node.translateX = if(offsetX <> 300 - image.width) 300 - image.width
else event.sceneX - X;

def offsetY = event.sceneY - Y;
event.node.translateY = if(offsetY <> 240 - image.height) 240 - image.height
else event.sceneY - Y;
}

L'applicazione é così completa. Rivediamo ora il codice completo:

package uiTutotial;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.Group;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

def playNormal = Image { url: "{__DIR__}play_onMouseExited.png" };
def playPressed = Image { url: "{__DIR__}play_onMousePressed.png" };
def stopNormal = Image { url: "{__DIR__}stop_onMouseExited.png" };
def stopPressed = Image { url: "{__DIR__}stop_onMousePressed.png" };

var image = playNormal;
var button = ImageView {image: bind image}

// event handling
var mode = true; //true for Play, false for Stop
var X: Number;
var Y: Number;
// event handling

Stage {
title: "Play Button"
scene: Scene {
width: 300
height: 240
content: Group {
content: button

// event handling
onMousePressed: function(event) {
X = event.sceneX - event.node.translateX;
Y = event.sceneY - event.node.translateY;
image = if (mode) { playPressed; } else { stopPressed; };
}
onMouseReleased: function(event) {
image = if(mode) stopNormal else playNormal;
mode = not mode;
}
onMouseDragged: function(event) {
def offsetX = event.sceneX - X;
event.node.translateX = if(offsetX <> 300 - image.width) 300 - image.width
else event.sceneX - X;

def offsetY = event.sceneY - Y;
event.node.translateY = if(offsetY <> 240 - image.height) 240 - image.height
else event.sceneY - Y;
} // function
} // group
} // scene
}

Desktop Profile

Scarichiamoci dal sito Sun altre due immagini che ci torneranno utili adesso, la rappresentazione del bottone al passaggio del mouse quando in stato Play e in stato Stop, e salviamole nel folder del nostro progetto.

...
def playHover = Image { url: "{__DIR__}play_onMouseEntered.png"};
def stopHover = Image { url: "{__DIR__}stop_onMouseEntered.png"};


Tooltip

Per aggiungere un Tooltip abbiamo bisogno di far riferimento alle classi Text, Font e Color, che quindi importiamo nel nostro script, dichiariamo quindi il nostro tooltip che avrà il testo definito in funzione della modalità corrente, e la sua posizione sarà legata alla posizione corrente del nostro bottone:

import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
...
var tooltip = Text {
content: bind if (mode) "Play Button" else "Stop Button"
translateX: bind button.translateX
translateY: bind button.translateY + 80
opacity: 0.0
font: Font { size: 12, name: "Tahoma" }
fill: Color.BLACK
...
Stage {
...
scene: Scene {
...
content: Group {
content: [button, tooltip]
...
}
}
};

L'ingresso del puntatore

Vogliamo che il tooltip che abbiamo definito venga mostrato all'entrata del puntatore nell'area del bottone, quindi gestiamo l'evento onMouseEntered. Per rendere più piacevole l'effetto usiamo una Timeline (classe che importiamo) che rende visibile il tooltip gradualmente in mezzo secondo, inoltre cambiamo anche la gestione dell'evento onMouseReleased, visto che vogliamo visualizzare le nuove immagini del bottone:

import javafx.animation.Timeline;
...
def appear = Timeline {
keyFrames: [
at(0s) { tooltip.opacity => 0.0 },
at(0.5s) { tooltip.opacity => 1.0 }
]
}
...
Stage {
...
scene: Scene {
...
content: Group {
...
onMouseReleased: function(event) {
image = if(mode) stopHover else playHover;
mode = not mode;
}
onMouseEntered: function(event) {
image = if (mode) playHover else stopHover;
appear.rate = 1;
appear.play();
}
} // group
} // scene
}

L'uscita del puntatore

In pratica, quando il puntatore esce dall'area del bottone, facciamo l'opposto di quello che abbiamo fatto all'ingresso:

onMouseExited: function(event) {
image = if (mode) playNormal else stopNormal;
appear.rate = -1;
appear.play();
}

Rivediamo il codice con le ultime modifiche, e con questo chiudamo lezione e tutorial.

package uiTutorial;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.Group;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

import javafx.scene.paint.Color; // tooltip
import javafx.scene.text.Font; // tooltip
import javafx.scene.text.Text; // tooltip
import javafx.animation.Timeline; // tooltip

def playNormal = Image { url: "{__DIR__}play_onMouseExited.png" };
def playPressed = Image { url: "{__DIR__}play_onMousePressed.png" };
def stopNormal = Image { url: "{__DIR__}stop_onMouseExited.png" };
def stopPressed = Image { url: "{__DIR__}stop_onMousePressed.png" };
def playHover = Image { url: "{__DIR__}play_onMouseEntered.png"};
def stopHover = Image { url: "{__DIR__}stop_onMouseEntered.png"};

var image = playNormal;
var button = ImageView {image: bind image}

// event handling
var mode = true; //true for Play, false for Stop
var X: Number;
var Y: Number;
// event handling

var tooltip = Text {
content: bind if (mode) "Play Button" else "Stop Button"
translateX: bind button.translateX
translateY: bind button.translateY + 80
opacity: 0.0
font: Font { size: 12, name: "Tahoma" }
fill: Color.BLACK
};

def appear = Timeline {
keyFrames: [
at(0s) { tooltip.opacity => 0.0 },
at(0.5s) { tooltip.opacity => 1.0 }
]
}

Stage {
title: "Play Button"
scene: Scene {
width: 300
height: 240
content: Group {
content: [button, tooltip]

// event handling
onMousePressed: function(event) {
X = event.sceneX - event.node.translateX;
Y = event.sceneY - event.node.translateY;
image = if (mode) { playPressed; } else { stopPressed; };
}
onMouseReleased: function(event) {
image = if(mode) stopHover else playHover;
mode = not mode;
}
onMouseDragged: function(event) {
def offsetX = event.sceneX - X;
event.node.translateX = if(offsetX <> 300 - image.width) 300 - image.width
else event.sceneX - X;

def offsetY = event.sceneY - Y;
event.node.translateY = if(offsetY <> 240 - image.height) 240 - image.height
else event.sceneY - Y;
}
onMouseEntered: function(event) {
image = if (mode) playHover else stopHover;
appear.rate = 1;
appear.play();
}
onMouseExited: function(event) {
image = if (mode) playNormal else stopNormal;
appear.rate = -1;
appear.play();
}
} // group
} // scene
}

Oggetti animati

Lezione numero sette del corso introduttivo SDN (Sun Developer Network) sulla costruzione di applicazioni GUI con JavaFX.

Le librerie JavaFX forniscono un supporto integrato alla creazione di animazioni. In questa lezione si mostra come costruire un oggetto UI e animarlo usando l'interpolazione lineare. E' un esempio piuttosto completo, usando la sintassi dichiarativa, il data binding, grafica e funzionalità specifiche dei nodi.

Iniziamo creando un applicazione per il common profile, aggiungeremo poi funzionalità specifiche dell'ambiente desktop.

Common Profile

L'applicazione che creiamo mostrerà una nuvoletta che si muove su un fondale stabilito, rimbalzando sui bordi.

La finestra dell'applicazione con un immagine sullo sfondo

Come al solito, partiamo definendo stage e scena. La nostra scena ha come colore di sfondo il bianco e come contenuto iniziale l'immagine di sfondo che
abbiamo scaricato qua dal sito Sun e messo nella nostra directory locale del progetto:

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

Stage {
title: "Cloud"
scene: Scene {
fill: Color.WHITE
content: [
ImageView {
image: Image { url: "{__DIR__}sun.jpg" }
} // imageView
] // content
} // scene
} // stage

Non avendo specificato la dimensione della nostra scena, questa si adatterà al contenuto, e sarà quindi determinata dalla dimensione dell'immagine.

La nuvola

Per disegnare la nuvola usiamo la classe Path, che ci permette di creare una forma custom, e la coloriamo dandole sfumature d'azzurro, usando la classe LinearGrandient.

import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.ArcTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
...
// nel content della scene, dopo l'immagine di sfondo:
Path {
stroke: Color.DODGERBLUE
fill: LinearGradient {
proportional: false, startX:60, startY:10, endX:10, endY:80
stops: [
Stop { offset: 0.0, color: Color.DODGERBLUE },
Stop { offset: 0.5, color: Color.LIGHTSKYBLUE },
Stop { offset: 1.0, color: Color.WHITE }
]
}
elements: [
MoveTo {x: 15, y: 15 },
ArcTo {x: 50, y: 10, radiusX: 20, radiusY: 20, sweepFlag: true},
ArcTo {x: 70, y: 20, radiusX: 20, radiusY: 20, sweepFlag: true},
ArcTo {x: 50, y: 60, radiusX: 20, radiusY: 20, sweepFlag: true},
ArcTo {x: 20, y: 50, radiusX: 10, radiusY: 5, sweepFlag: true},
ArcTo {x: 15, y: 15, radiusX: 10, radiusY: 10, sweepFlag: true},
]
} // path

La nostra nuvola ha un bordo (stroke) dodgerblue, é colorata in tre colori, da dodgerblue a bianco, a partire da (60, 10) a (10, 80).
Il bordo é costruito a partire dal punto (15,15) (la classe MoveTo crea il punto iniziale di un Path) per proseguire a (50, 10) tirando un arco che va verso l'esterno della superficie determinata (sweepFlag é true, se fosse false ci sarebbe come un "morso" nella nuvola)

Movimento orizzontale

Diamo movimento alla nuvola, per cominciare orizzontalmente. Per far ciò assegnamo una traslazione al nostro oggetto Path che definisce la nuvola fatta così:

// nel content della scena
...
Path {
translateX: bind x, translateY: 100
...
}

Dunque, la traslazione sull'asse y é fissata a 100, mentre sull'asse x é legata alla variabile x, che dovremmo definire prima.

In JavaFX viene supportato il concetto di animazione key frame. Lo stato di animazione di un oggetto viene definito dichiarando i key frame della scena in certi momenti. Specificato questo, il sistema può eseguire automaticamente l'animazione.

Vogliamo muovere la nuvola dall'ascissa 0 a 158. Il punto finale é determinato dalla dimensione del fondale (largo 241 pixel) e della nostra nuvola (che ha dimensioni 83x64), e 241 - 83 = 158.

Creiamo dunque un oggetto Timeline, che definisce al suo interno due KeyFrame, che saranno legati fra loro mediante interpolazione lineare.
Definiamo inoltre la variabile x, che verrà utilizzata nell'oggetto Path per permettere la traslazione della nostra nuvola.

import javafx.animation.Interpolator;
import javafx.animation.Timeline;

var x: Number;

Timeline {
keyFrames: [
at (0s) {x => 0.0},
at (4s) {x => 158.0 tween Interpolator.LINEAR}
]

repeatCount: Timeline.INDEFINITE
autoReverse: true
}.play();

L'oggetto Timeline che abbiamo costruito definisce la nostra animazione. Abbiamo dichiarato nella sua componente keyFrames due KeyFrame, usando la sintassi specifica JavaFX per questo tipo di oggetti, il primo al tempo 0s (dove s sta per secondi) associa alla variabile x il valore 0 (si noti l'operatore => che indica la chiamata ad un costruttore per una lista di valori), il secondo al tempo 4s associa a x il valore 158. Inoltre viene specificato mediante l'operatore tween che tipo di interpolazione andrà fatta tra i due KeyFrame definiti, in questo caso, una interpolazione lineare.

Inoltre specifichiamo che l'animazione va ripetuta indefinitivamente e che si applica l'autoreverse.

Nota che sull'oggetto costruito é applicato il metodo play(), per far partire immediatamente l'animazione.

Ci sono diversi tipi di interpolazione, a seconda dell'effetto che di vuole ottenere, ad esempio Interpolator.EASEBOTH, causa un simpatico rallentamento dell'azione avvicinandosi al KeyFrame.

Movimento verticale

E' semplice ora aggiungere movimento verticale, dovremmo semplicemente modificare il codice aggiungendo una variabile numerica (y) che sarà gestita da un'altro oggetto Timeline e che sarà legata alla proprietà translateY dell'oggetto Path:

var y: Number;
...
Timeline {
repeatCount: Timeline.INDEFINITE
autoReverse: true
keyFrames: [
at (0s) {y => 0.0},
at (7s) {y => 258.0 tween Interpolator.LINEAR},
]
}.play();
...
// nel content della scena
Path {
...
translateY: bind y
...
}

A seguire, il codice completo per lo script JavaFX realizzato fino a questo momento:

package uiTutorial;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

import javafx.scene.paint.LinearGradient; // cloud
import javafx.scene.paint.Stop; // cloud
import javafx.scene.shape.ArcTo; // cloud
import javafx.scene.shape.MoveTo; // cloud
import javafx.scene.shape.Path; // cloud

import javafx.animation.Interpolator; // key frame animation
import javafx.animation.Timeline; // key frame animation

// key frame animation
var x: Number;
var y: Number;

Timeline {
repeatCount: Timeline.INDEFINITE
autoReverse: true

keyFrames: [
at (0s) {x => 0.0},
at (4s) {x => 158.0 tween Interpolator.LINEAR }
]
}.play();

Timeline {
repeatCount: Timeline.INDEFINITE
autoReverse: true
keyFrames: [
at (0s) {y => 0.0},
at (7s) {y => 258.0 tween Interpolator.LINEAR},
]
}.play();
// key frame animation done

Stage {
title: "Cloud"
scene: Scene {
fill: Color.WHITE
content: [
ImageView {
image: Image { url: "{__DIR__}sun.jpg" }
} // imageView

Path {
// animation
translateX: bind x, translateY: bind y

stroke: Color.DODGERBLUE
fill: LinearGradient {
proportional: false, startX:60, startY:10, endX:10, endY:80
stops: [
Stop { offset: 0.0, color: Color.DODGERBLUE },
Stop { offset: 0.5, color: Color.LIGHTSKYBLUE },
Stop { offset: 1.0, color: Color.WHITE }
]
}
elements: [
MoveTo {x: 15, y: 15 },
ArcTo {x: 50, y: 10, radiusX: 20, radiusY: 20, sweepFlag: true},
ArcTo {x: 70, y: 20, radiusX: 20, radiusY: 20, sweepFlag: true},
ArcTo {x: 50, y: 60, radiusX: 20, radiusY: 20, sweepFlag: true},
ArcTo {x: 20, y: 50, radiusX: 10, radiusY: 5, sweepFlag: true},
ArcTo {x: 15, y: 15, radiusX: 10, radiusY: 10, sweepFlag: true},
]
} // path
] // content
} // scene
} // stage

Desktop Profile

Approfittiamo dei migliori effetti messi a disposizione dal profilo desktop per rendere la nostra applicazione un poco più piacevole.

import javafx.scene.effect.Lighting;
import javafx.scene.effect.light.DistantLight;
...
// nel content della scena
Path {
// desktop enhancement
effect: Lighting{ light: DistantLight{azimuth: 90} }
...
}

L'effetto di una fonte luminosa distante dà una prospettiva alla nostra nuvola che risulta così più realistica.

Layout di elementi GUI

La sesta lezione del tutorial introduttivo sun sulla costruzione di applicazioni GUI con JavaFX é dedicato ai layout. Lo scopo di un layout é quello di permetterci di gestire componenti GUI senza specificare coordinate assolute. Questo ci dà, generalmente, una maggiore flessibilità.

Per dimostrare ciò costruiamo un piccolo script fx che visualizza un simulazione di un semaforo regolato da un gruppo di bottoni radio. Quando un bottone é selezionato il cerchietto che rappresenta il colore ad esso collegato appare colorato, altrimenti viene lasciato in grigio.

La finestra dell'applicazione

Creiamo uno script, a nome trafficLight.fx ad esempio, e dichiariamo lo stage con la annessa scena su cui lavoriamo

import javafx.stage.Stage;
import javafx.scene.Scene;

Stage {
title: "Traffic lights"
scene: Scene {
width: 210, height: 90
} // scene
}

Bottoni

Creiamo un gruppo di bottoni, vogliamo che si possa scegliere un solo bottone alla volta, quindi importiamo e usiamo la classe ToggleGroup. I bottoni utilizzati sono di tipo RadioButton.

import javafx.scene.control.RadioButton;
import javafx.scene.control.ToggleGroup;
...
def group = ToggleGroup { };
def choiceText = ["STOP", "READY", "GO"];

def choices = for (text in choiceText) {
RadioButton {
toggleGroup: group
text: text
} // radioButton
}

choices[0].selected = true;

La costante choices contiene un array di radio buttons generati dal for che cicla sugli elementi di choiceText, a sua volta un array di stringhe.
Ogni radio button ha quindi la corrispondente stringa definita sopra come text, e tutti quanti hanno come toggleGroup la costante group sopra definita.
Nota che il toggleGroup sarà gestito implicitamente dai singoli bottoni.
Come ultima istruzione marchiamo come attivo il primo bottone, ovvero il semaforo sarà inizializzato col rosso.

Cerchi

Definiamo ora la costante che si occuperà di visualizzare il semaforo. Avremo bisogno delle classi Circle, Color, RadiantGradient e Stop, che importiamo.

import javafx.scene.shape.Circle;
import javafx.scene.paint.Color;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;
...
def colors = ["RED", "GOLD", "GREEN"];

def lights = for (color in colors) {
Circle {
centerX: 12, centerY: 12, radius: 12
stroke: Color.DARKGRAY
fill: bind RadialGradient {
centerX: 8, centerY: 8, radius: 12,
proportional: false
stops: [
Stop {offset: 0.0 color: Color.WHITE},
Stop {offset: 1.0 color:
if (choices[indexof color].selected)
then Color.web(color)
else Color.GRAY
} // stop
] // stops
} // radialGradient
} // circle
} // lights

Con un meccanismo simile a quello utilizzato per creare l'array di bottoni creiamo l'array lights che contiene oggetti di tipo Circle.
Da notare il binding dei cerchi ai bottoni: il colore che riempe (fill) il cerchio é determinato dallo stato dell'elemento di choices il cui indice é corrispondente all'indice del cerchio che stiamo colorando. Interessante notare anche l'uso del costrutto if then else, che agisce da operatore ternario, come il buon vecchio "? :".

Box

Inscatoliamo ora le nostre componenti in HBox (orizzontale, le componenti sono piazzate da sinistra verso destra) e VBox (verticale, le componenti vanno dall'alto in basso).
L'idea é quella di avere un HBox esterno che contiene un VBox per i bottoni e un HBox per i cerchi. L'HBox risultante sarà il content della nostra scena:

import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
...
HBox {
spacing: 20
content: [
VBox {
spacing: 10
content: [ for (i in [0..2]) choices[i] ]
}, // vbox
HBox {
spacing: 15,
content: lights,
translateY: 25
}
]
}

Nota che nell'HBox dei cerchi effettuiamo una traslazione verso il basso per far apparire il semaforo centrato, e non appiattito sull'alto della finestra.

Tile

A dire il vero risulta più naturale mantenere bottoni e luci in parallelo, e per far ciò, risulta comodo usare l'inscatolamento via Tile, una sorta di array bidimensionale di cui specifichiamo righe, colonne e contenuto di ogni cella.

import javafx.scene.layout.VBox;
...
Tile {
columns: 2, rows: 3, vgap: 5
content: for (i in [0..2]) [choices[i], lights[i]]
}

Anche come codide risulta decisamente più elegante.

Questo il codice complessivo che descrive lo script che abbiamo sviluppato. Nota l'uso del booleano isBox per permettere di testare i due differenti layout.

package uiTutorial;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.RadioButton; // choices
import javafx.scene.control.ToggleGroup; // choices
import javafx.scene.shape.Circle; // lights
import javafx.scene.paint.Color; // lights
import javafx.scene.paint.RadialGradient; // lights
import javafx.scene.paint.Stop; // lights
import javafx.scene.layout.HBox; // layout
import javafx.scene.layout.VBox; // layout
import javafx.scene.layout.Tile; // layout

// choices definition
def group = ToggleGroup { };
def choiceText = ["STOP", "READY", "GO"];

def choices = for (text in choiceText) {
RadioButton {
toggleGroup: group
text: text
} // radioButton
}

choices[0].selected = true;
// choices ready for use

// lights definition
def colors = ["RED", "GOLD", "GREEN"];

def lights = for (color in colors) {
Circle {
centerX: 12, centerY: 12, radius: 12
stroke: Color.DARKGRAY
fill: bind RadialGradient {
centerX: 8, centerY: 8, radius: 12,
proportional: false
stops: [
Stop {offset: 0.0 color: Color.WHITE},
Stop {offset: 1.0 color:
if (choices[indexof color].selected) // binding applies here
then Color.web(color)
else Color.GRAY
} // stop
] // stops
} // radialGradient
} // circle
} // lights

def isBox = true;
def content = if(isBox)
then HBox {
spacing: 20
content: [
VBox {
spacing: 10
content: [ for (i in [0..2]) choices[i] ]
}, // vbox
HBox {
spacing: 15,
content: lights,
translateY: 25
}
]
} // scene content version 1
else Tile {
columns: 2, rows: 3, vgap: 5
content: for (i in [0..2]) [choices[i], lights[i]]
} // scene content version 2

Stage {
title: "Traffic lights"
scene: Scene {
width: 210, height: 90
content: content;
}
}

Legare dati e oggetti UI

Quinta lezione del corso sun sulla creazione di applicazioni GUI con JavaFX, dedicata al data binding, ovvero il meccanismo secondo cui il cambiamento di una variabile determina l'aggiornamento di un'altra variabile in accordo con la relazione che si é definita tra le due.

E' una necessità comune dello sviluppo software, aggiornare un certo parametro ogni qual volta un altro parametro cambia. Nella programmazione con JavaFX, si può ottenere questo effetto col meccanismo del data binding. Si definisce una relazione tra due variabili qualunque in modo che ogni volta che una cambia, l'altra venga aggiornata. Ci pensa JavaFX a tener traccia di ogni cambiamento e di fare ogni aggiornamento necessario.

Per vedere come il data binding funziona creiamo una semplice applicazione il cui scopo é visualizza una sorta di emulazione di una eclisse: un cerchio il cui bordo é evidenziato in giallo ha il suo contenuto sfumato e colorato in rosso. Muovendo una barra di controllo, vogliamo che il cerchio dia l'impressione che si muova solo il bordo mentre il suo contenuto resti fermo.

La finestra dell'applicazione

Creiamo il nostro script JavaFX, dichiarando lo stage e la scena in questo modo:

import javafx.stage.Stage;
import javafx.scene.Scene;

Stage {
title: "Data Binding"
scene: Scene {
width: 220, height: 170
} // scene
}

Il cerchio

Aggiungiamo un import per le classi Circle e Color, e mettiamo il nostro cerchio con bordo giallo sulla scena:

...
import javafx.scene.shape.Circle;
import javafx.scene.paint.Color;

Stage {
...
scene: Scene {
...
content: Circle {
centerX: 60, centerY: 60, radius: 50
stroke: Color.YELLOW
} // circle
} // scene
}

Dato che non abbiamo specificato i colori della scena e dell'interno del cerchio, ce li troviamo entrambi nel loro default, rispettivamente bianco e nero.

Colore riempitivo per il cerchio

Usiamo la classe RadialGradient per dare un piacevole effetto al colore interno al cerchio. Aggiungiamo perciò l'import per questa classe e per Stop, usata per definire il gradiente e definiamo il riempitivo del cerchio assegnando alla sua proprietà fill il gradiente radiale nei colori bianco e rosso:

...
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;


Stage {
...
scene: Scene {
...
content: Circle {
...
fill: RadialGradient {
centerX: 60, centerY : 60, radius: 50
focusX: 60, focusY: 30
proportional: false

stops: [
Stop {offset: 0 color: Color.RED},
Stop {offset: 1 color: Color.WHITE}
]
} // radialGradient
} // circle
} // scene
}

Barra di scorrimento

Aggiungiamo ora una barra di scorrimento sotto il cerchio, importiamo la classe Slider e aggiungiamola ai nostri oggetti sulla scena. Nota che, dato che ora gli oggetti sulla scena sono due, modifichiamo il content per fare riferimento ad un array, e non ad un singolo oggetto:

...
import javafx.scene.control.Slider;

...
Stage {
...
scene: Scene {
...
content: [
Circle {
...
} // circle

Slider {
min: 0, max: 60, value : 0
translateX: 10, translateY: 120
}
] // content
} // scene
}

Nota che la posizione dell'oggetto slider nella scena é determinata dalle sue variabili translateX e translateY.

La relazione di binding

Per poter definire la relazione di binding all'interno del cerchio, l'oggetto slider deve essere precendentemente definito (e referenziabile). Per far ciò spostiamo la sua definizione prima della creazione dello stage. Fatto questo, possiamo definire il centro del cerchio in funzione del valore dello slider:

...
def slider = Slider {
min: 0, max: 60, value : 0
translateX: 10, translateY: 120
}

Stage {
...
scene: Scene {
...
content: [
Circle {
centerX: bind slider.value + 60, centerY: 60, radius: 50
...
}, // circle
slider
] // content
} // scene
}

Nota che, avendo fatto ricorso a classi del common profile, l'applicazione può essere eseguita sia in ambiente desktop, che mobile.

Segue il codice completo dello script:

package uiTutorial;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.shape.Circle;
import javafx.scene.paint.Color;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;
import javafx.scene.control.Slider;

def slider = Slider {
min: 0, max: 60, value : 0
translateX: 10, translateY: 120
}

Stage {
title: "Data Binding"
scene: Scene {
width: 220, height: 170
content: [
Circle {
centerX: bind slider.value + 60, centerY: 60, radius: 50
stroke: Color.YELLOW
fill: RadialGradient {
centerX: 60, centerY : 60, radius: 50
focusX: 60, focusY: 30
proportional: false

stops: [
Stop {offset: 0 color: Color.RED},
Stop {offset: 1 color: Color.WHITE}
]
} // radialGradient
} // circle

slider
] // content
} // scene
}

Oggetti grafici

Quarta lezione del tutorial sun sulla creazione di applicazioni GUI con javaFX, qui si riprende il discorso fatto nella prima lezione, mostrando come combinare gli oggetti grafici forniti da javaFX per crearne di nuovi da usare nelle nostre applicazioni.

Iniziamo vedendo un esempio per il common profile, poi lo estenderemo per il desktop profile che permette di applicare effetti che lo rendono ancor più attraente.

Common Profile

Diciamo che vogliamo creare un lettore audio. L'esempio che trattiamo qui si limita a mostrare come creare il bottone "play", una freccia che, pigiata, lo farà partire.

Per crearlo, combineremo oggetti e effetti resi disponibili da javaFX e che abbiamo introdotto nella prima lezione di questo tutorial.

Prima di costruire un nuova componente, é una buona idea dare un'occhiata in giro per vedere cosa c'é già disponibile in giro. Con javaFX sono messi a disposizione una serie di utili componenti nel package standard javafx.scene.control.

In ogni caso, il nostro primo passo é quello di creare un nuovo file, chiamiamolo play.fx, ad esempio.

La finestra della applicazione

Ormai la cosa ci dovrebbe riuscire naturale, importiamo la classe Stage e ne dichiariamo una istanza, assegnando al su attributo title quello sarà il titolo la finestra principale della nostra applicazione.

import javafx.stage.Stage;

Stage {
title: "Play Button"
}

La scena

All'interno dello stage prepariamo la scena in cui verrà mostrato il nostro bottone. Aggiungiamo perciò un import per le classi Scene, Color e Group, dichiariamo un oggetto Scene all'interno del nostro Stage, definiamo il colore di sfondo che vogliamo usare e specifichiamo gli oggetti contenuti nella nostra scena inizializzando la sua proprietà content con un instanza della classe Group. Al momento non ci sono oggetti, perciò definiamo come content del nostro gruppo solo un'array vuoto:

...
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.Group;

Stage {
...

scene: Scene {
width: 250, height: 350
fill: Color.WHITE
content: Group {
content: []
} //Group
} //Scene
}

Nota che la definizione delle dimensione della scena non sarebbe richiesta se la nostra applicazione corresse solo su un mobile, dato che verrebbe assunto per default che usasse tutto lo spazio a disposizione. Nel nostro caso vogliamo che sia utilizzabile anche per desktop.

Lo sfondo del bottone

Vogliamo che il nostro bottone abbia un sfondo un poco elaborato. Lavoriamo con rettangoli, perciò aggiungiamo l'import per la classe Rectangle. Vogliamo usare sfumature nei colori e per far ciò importiamo le classi LinearGradient e Stop.
L'idea é quella di avere tre rettangoli sovrapposti, racchiusi da una cornice ad angoli smussati, in varie tonalità di blu sfumate.

La struttura del nostro script verrà perciò estesa in questo modo:

...
import javafx.scene.shape.Rectangle;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;

Stage {
...

scene: Scene {
...
content: Group {
content: [
Rectangle {
...
}

Rectangle {
...
}

Rectangle {
...
}

Rectangle {
...
}
]
} //Group
} //Scene

Il primo rettangolo descriverà l'area in alto:

Rectangle {
x: 48, y: 46, width: 150, height: 59
stroke: Color.GREEN
fill: LinearGradient {
startX: 0.0, startY: 1.0, endX: 0.0, endY: 0.0,
proportional: true
stops: [
Stop { offset: 0.0, color: Color.web("#aaeeff") },
Stop { offset: 1.0, color: Color.web("#66aacc") }
]
} // fill
}

Si tratta di un rettangolo contornato da una sottile riga verde, sfumato usando due tonalità di blu.
Nota che il gradiente utilizzato per definire la sfumatura ha la proprietà proportional definita come true, in questo modo definiamo gli stop in formato relativo, decisamente più comodo.

Il secondo rettangolo definisce l'area centrale, é molto simile al primo, la differenza più grossa é che non ha un contorno:

Rectangle {
x: 48, y: 106, width: 150, height: 64
fill: LinearGradient {
startX: 0.0, startY: 1.0, endX: 0.0, endY: 0.0,
proportional: true
stops: [
Stop { offset: 0.0, color: Color.web("#99ddff") },
Stop { offset: 1.0, color: Color.web("#337799") }
]
} // fill
}

Il terzo rettangolo definisce l'area in basso:

Rectangle {
x: 48, y: 170, width: 150, height: 25
fill: LinearGradient {
startX: 0.0, startY: 1.0, endX: 0.0, endY: 0.0,
proportional: true
stops: [
Stop { offset: 0.0, color: Color.web("#337799") },
Stop { offset: 1.0, color: Color.web("#99ddff") }
]
} //fill
}

E infine usiamo un quarto rettangolo per incorniciare i tre di cui sopra:

Rectangle {
x: 48, y: 45, width: 150, height: 150
arcWidth: 15, arcHeight: 15,
stroke: Color.web("#337799")
strokeWidth: 5
fill: null
}

Notiamo che questo rettangolo é arrotondato (nota la definizione delle variabili arcWidth e arcHeight diverse da zero) ha un bordo piuttosto spesso (5 pixel) e, soprattutto, non ha nulla al suo interno (la variabile fill é settata a null). Dunque é solo una cornice, che lascia vedere quanto c'é sotto.

Il tasto Play

Abbiamo definito il fondale, ora vogliamo aggiungere un tasto che sarà composto da un cerchio con un triangolo al suo interno, che rappresenta una freccia verso destra. Anzi, per dare un'elegante effetto tridimensionale, di triangoli ne facciamo due, sovrapposti.

Avremo bisogno di altre due classi, Circle e Polygon, che importiamo. Poi aggiungeremo i nostri tre nuovi oggetti al gruppo sulla scena:

...
import javafx.scene.shape.Circle;
import javafx.scene.shape.Polygon;
...
Group {
content: [
...
Circle {
centerX: 122 centerY: 122 radius: 38
fill: LinearGradient {startX: 0.0, startY: 1.0, endX: 0.0, endY: 0.0,
proportional: true
stops: [
Stop {offset: 0.0 color: Color.web("#66aacc")},
Stop {offset: 1.0 color: Color.web("#99ddff")}
]
} // fill
stroke: Color.web("#66aacc")
strokeWidth: 2.0
} // circle

Polygon {
points: [142.0, 126.0, 113.0, 108.0, 111.0, 143.0]
fill: Color.web("#337799")
} // lower triangle

Polygon {
points: [142.0, 123.0, 110.0, 105.0, 110.0, 140.0]
fill: Color.web("#ffffff")
} // upper triangle
]

E questo é tutto per il nostro esempio in ambiente comune.

Desktop Profile

L'ambiente desktop permette di sbizzarrirsi un po' di più con gli effetti visuali.

Una cosa che possiamo fare, é ottenere l'effetto tridimensionale sul triangolo senza raddoppiare gli oggetti, ma aggiungendo al triangolo una ombreggiatura.

Effetto DropShadow

Importiamo la classe DropShadow e ridefiniamo il nostro triangolo usando un solo poligono definito come segue:

import javafx.scene.effect.DropShadow;
...
Polygon {
points: [142.0, 123.0, 110.0, 105.0, 110.0, 140.0]
fill: Color.web("#ffffff")
effect: DropShadow {
color: Color.web("#337799")
offsetX: 2, offsetY: 5
}
}


Effetto reflection

Come ultima cosa, aggiungiamo un riflesso alla nostra immagine, importiamo la classe Reflection, e aggiungiamo un effetto al nostro gruppo di oggetti sulla scena:

import javafx.scene.effect.Reflection;
Group {
content: [
...
]
effect: Reflection { fraction: 0.5 topOpacity: 0.5 topOffset: 0.5 }
} //Group

Come riferiment, lascio qui a seguire il codice completo per lo script di questa lezione:

package uiTutorial;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.Group;
import javafx.scene.shape.Rectangle;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Polygon;
import javafx.scene.effect.DropShadow; // for desktop enhancement
import javafx.scene.effect.Reflection; // for desktop enhancement

Stage {
title: "Play Button"

scene: Scene {
width: 250, height: 350
fill: Color.WHITE
content: Group {
content: [
Rectangle {
x: 48, y: 46, width: 150, height: 59
stroke: Color.GREEN
fill: LinearGradient {
startX: 0.0, startY: 1.0, endX: 0.0, endY: 0.0,
proportional: true
stops: [
Stop { offset: 0.0, color: Color.web("#aaeeff") },
Stop { offset: 1.0, color: Color.web("#66aacc") }
]
} // fill
} // top rectangle

Rectangle {
x: 48, y: 106, width: 150, height: 64
fill: LinearGradient {
startX: 0.0, startY: 1.0, endX: 0.0, endY: 0.0,
proportional: true
stops: [
Stop { offset: 0.0, color: Color.web("#99ddff") },
Stop { offset: 1.0, color: Color.web("#337799") }
]
} // fill
} // central rectangle

Rectangle {
x: 48, y: 170, width: 150, height: 25
fill: LinearGradient {
startX: 0.0, startY: 1.0, endX: 0.0, endY: 0.0,
proportional: true
stops: [
Stop { offset: 0.0, color: Color.web("#337799") },
Stop { offset: 1.0, color: Color.web("#99ddff") }
]
} //fill
} // bottom rectangle

Rectangle {
x: 48, y: 45, width: 150, height: 150
arcWidth: 15, arcHeight: 15,
stroke: Color.web("#337799")
strokeWidth: 5
fill: null
} // border rectangle

Circle {
centerX: 122 centerY: 122 radius: 38
fill: LinearGradient {startX: 0.0, startY: 1.0, endX: 0.0, endY: 0.0,
proportional: true
stops: [
Stop {offset: 0.0 color: Color.web("#66aacc")},
Stop {offset: 1.0 color: Color.web("#99ddff")}
]
} // fill
stroke: Color.web("#66aacc")
strokeWidth: 2.0
} // circle

/*
// common profile version for the triangle
Polygon {
points: [142.0, 126.0, 113.0, 108.0, 111.0, 143.0]
fill: Color.web("#337799")
} // lower triangle

Polygon {
points: [142.0, 123.0, 110.0, 105.0, 110.0, 140.0]
fill: Color.web("#ffffff")
} // upper triangle
*/
Polygon {
points: [142.0, 123.0, 110.0, 105.0, 110.0, 140.0]
fill: Color.web("#ffffff")
effect: DropShadow {
color: Color.web("#337799")
offsetX: 2, offsetY: 5
}
} // triangle as desktop enhancement
] // group content

// effect as desktop enhancement
effect: Reflection { fraction: 0.5 topOpacity: 0.5 topOffset: 0.5 }
} //Group
} //Scene
}

Presentare oggetti sulla scena

Terza lezione del corso introduttivo sun per la creazione di GUI con javaFX.

Qui si spiega l'architettura sottesa a javaFX che definisce la scena come un grafo contenente nodi che sono poi gli oggetti che vogliamo visualizzare.

Le classi coinvolte in questa architettura sono Scene, Node e Group.

La scena é un grafo che ha una struttura dati ad albero che definisce una gerarchia di oggetti grafici. Ogni elemento nel grafo é detto nodo. Ogni nodo ha un genitore, ad eccezione della radice (root node) che non ha un genitore. Ogni nodo o é una foglia (leaf) o é un ramo (branch). Una foglia non ha figli, un ramo ha zero o più figli.

I nodi in javaFX gestiscono diversi tipi di contenuti come componenti UI, forme, testi, immagini, e media. I nodi possono essere trasformati, animati, e si possono applicare loro svariati effetti.

In questa lezione si creerà una applicazione che contiene oggetti grafici un po' di tutti tipi.

Lavoriamo con il common profile, in modo da poter usare la nostra applicazione in entrambi gli ambienti desktop e mobile.

Alla base di tutto c'é la scena che, in javaFX, é un contenitore di nodi.

La classe base della gerarchia di classi che possono essere inserite in una scena é javafx.scene.Node, quindi tutte le altre classi che possono essere gestite come nodi, come ad esempio javafx.scene.shape.Circle, derivano da Node.

Per convenzione in Node si usa un sistema di ascisse e ordinate dove X aumenta muovendosi verso destra e Y aumenta scendendo verso il basso. Dunque l'angolo (0,0) é in alto a sinistra.

E' possibile raggruppare più nodi per trattarli come un unica entità, usando la classe javafx.scene.Group, che rappresenta per l'appunto un gruppo di nodi.

La finestra dell'applicazione

Creamo lo stage su cui monteremo la nostra scena:

import javafx.stage.Stage;

Stage {
title: "Nodes"
}

La scena

Per montare la scena nel nostro stage, aggiungiamo l'import per la classe Scene e Color (vogliamo infatti che la nostra scena abbia un colore di fondo diverso dal default).
Quindi assegnamo alla proprietà scene del nostro stage un'istanza di classe Scene con alcuni proprietà specificate (colore, e dimensioni):

...
import javafx.scene.Scene;
import javafx.scene.paint.Color;

Stage {
...
scene: Scene {
fill: Color.LIGHTBLUE
width: 220
height: 170
}
}

Il primo nodo

Nella variabile content della scena sono definiti i nodi che sono sulla scena stessa. Per aggiungere alla scena il nostro primo nodo, aggiungiamo l'import per la classe Circle (visto che aggiungeremo un cerchio) e assegnamo al content della nostra scena un oggetto Circle che definiamo come mostrato qua a seguire:

...
import javafx.scene.shape.Circle;

Stage {
...
scene: Scene {
...
content: Circle {
centerX: 50, centerY: 50, radius: 50
stroke: Color.YELLOW
fill: Color.WHITE
}
}
}

Un nodo di testo con trasformazione

Aggiungiamo un secondo nodo alla nostra scena, per far questo dobbiamo dire a content che ci saranno più oggetti dentro di lui, non possiamo più fare un semplice assegnamento di un oggetto ma abbiamo bisogno di definire un array. Per far ciò usiamo un parentesi quadra.
In pratica, avremo una definizione di content simile a questa:

content: [ Circle {...}, Text {...} ]

Visto che vogliamo un nodo di tipo testo, aggiungiamo l'import per la classe Text, e quindi aggiungiamo la definizione del nostro nodo nel content della nostra scena.

Ma non ci basta mettere una semplice stringa di testo, la vogliamo modificare per farla stare dentro il cerchio, ovvero, vogliamo applicare una trasformazione al nostro oggetto testo, quindi aggiungeremo l'import anche della classe Transform.
Nel nostro oggetto di testo assegneremo quindi alla sua variabile transform il risultato del metodo statico Transform.rotate(), specificando un angolo di 33 gradi e un punto pivotale per la rotazione in (10, 100):

...
import javafx.scene.text.Text;
import javafx.scene.transform.Transform;

Stage {
...
scene: Scene {
...
content: [
Circle {
...
},
Text {
transforms: Transform.rotate(33, 10, 100)
content: "Duke"
}
]
}
}

Un nodo immagine

Per mettere un immagine in scena abbiamo bisogno di due classi, una per rappresentare l'immagine in sè, e quindi aggiungeremo una import per la classe Image, e una per rappresentare la vista dell'immagine stessa, come verrà gestita dalla scena, altra import per ImageView.
Per specificare da dove va presa un'immagine c'é nella classe Image una variabile, url, che é per l'appunto l'indirizzo da cui possiamo reperire la nostra immagine. Qui usiamo un file locale alla nostra macchina, e quindi specifichiamo di andarla a prendere nella directory corrente (accertiamoci che il file ci sia, possiamo copiarlo lì dal tutorial javaFX). Non specifichiamo dove posizionare l'immagine, e perciò verrà messa nella posizione di default, angolo di sinistra in alto a (0,0):

...
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

Stage {
...
scene: Scene {
...
content: [
Circle {
...
},
Text {
...
},
ImageView {
image: Image {url: "{__DIR__}dukewave.png"}
}
]
}
}

Nodi in gruppo

Tutto ok, ma ci piacerebbe spostare il nostro lavoretto un po' verso il centro. Per far ciò raggruppiamo tutti i nostri oggetti grafici e applichiamo una traslazione al gruppo.
Ci servirà importare la classe Group e cambiare il content della nostra scena. Nella scena torneremo ad avere un solo oggetto: il gruppo che contiene tutti gli altri oggetti.

...
import javafx.scene.Group;

Stage {
...
scene: Group {
translateX: 55, translateY: 10
content: [
Circle {
...
},
Text {
...
},
ImageView {
...
}
]
} // Group
} // Scene
} // Stage

E questo é tutto. Ci siamo costruiti un gruppo di oggetti visuali e li abbiamo mossi sulla scena. Non male, dopotutto.

A seguire, il codice completo dello script:

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.text.Text;
import javafx.scene.transform.Transform;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.Group;

Stage {
title: "Nodes"
scene: Scene {
fill: Color.LIGHTBLUE
width: 220
height: 170
content: Group {
translateX: 55, translateY: 10
content: [
Circle {
centerX: 50, centerY: 50, radius: 50
stroke: Color.YELLOW
fill: Color.WHITE
},
Text {
transforms: Transform.rotate(33, 10, 100)
content: "Duke"
},
ImageView {
image: Image {url: "{__DIR__}dukewave.png"}
}
]
} // Group
} // Scene
} // Stage

Sintassi dichiarativa

Vediamo la seconda lezione del corso sun introduttorio alla costruzione di applicazioni GUI con javaFX.

Per spiegare l'approccio dichiarativo in questo contesto, si scrive un semplice applicazione javaFX che visualizza un cerchio con un rettangolo smussato sopra.

Dato che usiamo il common profile, l'applicazione funziona in entrambi i contesti desktop e mobile.

Creiamo dunque uno script fx nel file declaring.fx, che riempiremo passo dopo passo per realizzare la nostra applicazione.

Per creare la finestra principale della nostra applicazione dobbiamo semplicemente dichiarare l'oggetto letterale Stage.

Per personalizzare il nostro Stage, assegnamo un valore alla sua variabile - per iniziare gli assegnamo un titolo.

import javafx.stage.Stage;

Stage {
title: "Declaring Is Easy!"
}

Con questo codice abbiamo usato l'approccio dichiarativo per creare il palcoscenico (stage) della nostra applicazione, ovvero la sua finestra principale, e abbiamo assegnato un valore ad una sua proprietà (chiamata instance variable).

La documentazione di Stage ci mostra tutte le sue variabili disponibili, tra cui, oltre al titolo, vediamo che ci sono anche la sua altezza (height) e ampiezza (width). Possiamo modificare il nostro script per vederle in uso:

import javafx.stage.Stage;

Stage {
title: "Declaring Is Easy!"
height: 150
width: 300
}

Tra le proprietà dello stage c'é anche la scena, che é il "posto" dove avverranno le cose, ovvero dove metteremo i nostri oggetti che vogliamo visualizzare. Riscriviamo perciò il nostro script per includere una scena che, inizialmente, non conterrà alcunché.

import javafx.stage.Stage;
import javafx.scene.Scene;

Stage {
title: "Declaring Is Easy!"
scene: Scene {
width: 300
height: 250
content: [ ]
}
}

Abbiamo aggiunto l'import per la scena, e abbiamo assegnato alla proprietà relativa del nostro stage l'oggetto scena che abbiamo costruito al volo.
Notiamo che non é necessario definire le dimensioni dello stage, dato che abbiamo definito quelle della scena che é contenuta nello stage. Inoltre, se l'applicazione fosse per mobile, non dovremmo definire la dimensione, visto che si assumerebbe che venga usata tutta la (modesta) area a disposizione del dispositivo.

La scena é in pratica il nodo originario (root node) dell'albero di oggetti che sono posti sullo stage.
Nel suo contenuto (content) mettiamo i nodi che desideriamo presentare all'utente, che saranno, ad esempio, oggetti grafici, testo, o componenti di interfaccia.

Mettiamo ora qualcosa nel contenuto della scena, creiamo un cerchio in content:

...
import javafx.scene.shape.Circle;
import javafx.scene.paint.Color;
...
content: [
Circle {
centerX: 150
centerY: 120
radius: 80
fill: Color.MAROON
stroke: Color.INDIANRED
strokeWidth: 10.0
}
]
...

Abbiamo modificato il nostro script aggiungendo due import e dichiarando un oggetto di tipo Circle all'interno del contenuto della scena.
Il nostro cerchio ha centro nel punto (150, 120), un raggio di 80, é riempito col colore marrone, e ha un bordo (stroke) di ampiezza 10 e colore rosso indiano.

Aggiungiamo ora un rettangolo al nostro stage.

...
import javafx.scene.shape.Rectangle;
...
content: [
Circle { ... }
Rectangle {
x: 25, y: 80, width: 250, height: 80
arcWidth: 20 arcHeight: 20
fill: Color.web("#6699ff")
stroke: Color.web("#003399")
strokeWidth: 5.0
opacity: 0.5
}
]

Abbiamo aggiunto l'import per la classe Rectangle e abbiamo aggiunto una sua istanza al contenuto della scena.
Nota che abbiamo utilizzato una sintassi leggermente diversa (le virgole tra le proprietà sono opzionali e risultano comode per migliorare la leggibilità quando se ne scrivono diverse sulla stessa riga) e che abbiamo definito qui i colori usando, invece che una costante letterale, il metodo statico pubblico web() della classe Color, che converte in colore RGB una stringa che lo rappresenta in notazione esadecimale.
Abbiamo definito l'opacità (opacity) del nostro rettangolo a 0.5 in modo da lasciar vedere il cerchio sottostante - per default i colori definiti via Color.web() hanno una opacità pari a 1.
Gli angoli del rettangolo sono smussati in accordo alla definizione di arcWidth e arcHeight.

Notiamo infine che il primo oggetto messo nella scena resti sullo sfondo e venga coperto, nel caso di sovrapposizioni da chi viene dopo. Infatti permutando l'ordine degli oggetti posti in content, mettendo come primo nodo il rettangolo e come secondo il cerchio, otteniamo che il cerchio sia in primo piano (e dato che l'opacità é settata come default a 1, nasconde completamente il rettangolo nell'area di sovrapposizione).

Desktop profile

Parte finale della prima lezione del corso Oracle a introduzione su come scrivere gui con javaFX.

Per il "desktop", ovvero per gli ambienti che prevedano l'utilizzo di una periferica di output di dimensioni superiori a quelle dei pochi pollici dell'"handy", sono diponibili in ambiente javaFX un altro paio di caratteristiche interessanti: effetti e cursori.

Effetti

Per vedere gli effetti in azione, si scarichi lo script effects.fx.

Dal codice javaFX vediamo che per dare un effetto a un gruppo di oggetti usiamo classi del package javafx.scene.effect e javafx.scene.effect.light semplicemente specificando la proprietà effect dell'oggetto grafico che vogliamo modificare.

L'ombreggiatura applicata ad un elemento grafico gli dà un effetto tridimensionale, come se si librasse, o sprofondasse nel piano su cui é rappresentato.

Si usano due classi per generare una ombreggiatura: DropShadow e InnerShadow

Vediamo ad esempio il primo oggetto ombreggiato definito nello script, un gruppo comprendente un cerchio rosso con due linee che si incrociano al suo interno.

Al gruppo possiamo aggiungere l'effetto ombreggiatura, che possiamo variare in molti modi, ad esempio specificando il suo colore.

Group {
content: [
Circle{centerX: 30 centerY:40 radius:20 fill: Color.RED opacity: 0.8 cursor: Cursor.HAND},
Line {startX: 20 startY: 30 endX: 40 endY: 50 stroke: Color.WHITE strokeWidth: 2},
Line {startX: 20 startY: 50 endX: 40 endY: 30 stroke: Color.WHITE strokeWidth: 2}
]
effect: DropShadow{offsetX:5 offsetY:5 color: Color.BLACKS}
};

E' possibile creare ombreggiature più complesse mescolando diversi oggetti Shadow, usando la classe Blend

L'illuminazione gli dà anch'essa un effetto tridimensionale, ma paragonabile all'agire sull'elemento grafico come per pigiarlo nel piano o farlo saltar fuori.

Per creare l'effetto illuminazione usiamo la classe Lighting specificando il tipo di luce utilizzata nel suo parametro light.

Group {
content:[
Circle{centerX: 30 centerY:20 radius:20 fill: Color.RED opacity: 0.9},
Line {startX: 20 startY: 10 endX: 40 endY: 30 stroke: Color.WHITE strokeWidth: 2},
Line {startX: 20 startY: 30 endX: 40 endY: 10 stroke: Color.WHITE strokeWidth: 2}
]
effect: Lighting {light: DistantLight{azimuth: 90}}
};

E' possibile poi creare un effetto sfocamento usando le classi della famiglia Blur (BoxBlur, GaussianBlur, MotionBlur). Questo esempio mostra un rettangolo riempito con cerchi concentrici bianchi e verde scuro a cui viene applicato l'effetto GaussianBlur:

Rectangle {
x:5 y:25 width: 100 height: 50
fill: RadialGradient {
centerX: 55, centerY: 50, radius: 10, proportional: false
cycleMethod: CycleMethod.REFLECT
stops: [
Stop{offset: 0.3 color: Color.DARKGREEN},
Stop{offset: 1.0 color: Color.WHITE}
]
}
effect: GaussianBlur{radius: 5 }
};

Si può creare un riflesso dell'oggetto grafico, usando la classe Reflection. Nell'esempio lo si applica ad un testo:

Text {
x: 10 y: 42 content: "Reflection"
fill: Color.SEAGREEN
font: Font{size:20 name: "Verdana"}
effect: Reflection { fraction: 0.9 topOpacity: 0.9 topOffset: 0.1}
};

Per cambiare la luminescenza di una immagine si usa la classe Glow, come mostrato qui:

ImageView {
image: Image{url:"http://java.sun.com/docs/books/tutorial/images/penduke.gif"}
effect: Glow{level: 1}
};

Per virare in seppia un immagine usiamo l'effetto SepiaTone:

ImageView {
image: Image{url:"http://java.sun.com/docs/books/tutorial/images/penduke.gif"}
effect: SepiaTone {level: 0.8}
};

Si possono cambiare gli attributi del colore di un oggetto grafico con ColorAdjust:

ImageView {
image: Image{url:"http://java.sun.com/im/logo_sun_small_sdn.gif"}
effect: ColorAdjust{brightness: -0.2}
};

O, infine, trasformarlo mettendolo in prospettiva con PerspectiveTransform:

var transformed = ImageView {
image: Image{url: "http://java.sun.com/docs/books/tutorial/images/penduke.gif"}
effect: PerspectiveTransform {
ulx: 40 uly: 20
urx: 104 ury: 20
lrx: 134 lry: 115
llx: 10 lly: 105
}
}

Cursori

E' possibile specificare che forma abbia il cursore sopra una certa regione della finestra di visualizzazione. Vedi lo script cursor.fx per un esempio.

Il cursore é un attributo di un oggetto grafico, quindi lo settiamo come vediamo in questo esempio:

Group {
content:[
Circle{centerX: 30 centerY:40 radius:20 fill: Color.RED opacity: 0.8 cursor: Cursor.HAND},
Line {startX: 20 startY: 30 endX: 40 endY: 50 stroke: Color.WHITE strokeWidth: 2},
Line {startX: 20 startY: 50 endX: 40 endY: 30 stroke: Color.WHITE strokeWidth: 2}
]
effect: DropShadow{offsetX:5 offsetY:5 color: Color.BLACK}
};

I cursori a disposizione sono indicati nella classe javafx.scene.Cursor, e usati come visto sopra (Cursor.HAND).

Common Profile

Continuamo a vedere la prima lezione del corso Oracle a introduzione su come scrivere gui con javaFX. Attacchiamo ora il Common Profile. Questa lezione m'é stata cambiata sotto i piedi, infatti ci stavo lavorando sopra in questi giorni (inizio giugno 2009) proprio in corrispondenza con il rilascio della versione 1.2 di javaFX, che ha aggiunto molta roba in questo contesto.

Partiamo con gli elementi di GUI, lo script che li fa vedere in opera é UIControls.fx si trova sul sito sun. Si tratta di radio button, textbox, toggle button, check box, slider e progress bar che sono definiti nel package javafx.scene.control.

Poi si passa ai grafici. Il supporto ai quali viene fornito tramite il package javafx.scene.chart e sembra veramente notevole. Si possono costruire facilmente grafici con aree, areaChart.fx, a barre, barChart.fx, a bolle, bubbleChart.fx, a linee, lineChart.fx, a torta, pieChart.fx, o a punti, scatteredChart.fx, insomma, ce n'é per tutti i gusti.

E' estremamente semplice costruire un pannello per la gestione dei colori usando la classe javafx.scene.paint.Color, come illustrato nello script colors.fx.

Per la gestione di forme geometriche si fa riferimento al package javafx.scene.shape, e lo script shapes.fx ce ne da un esempio.

Sono a disposizione alcuni stili di riempimento per le forme geometriche, definiti nel package javafx.scene.paint, e che possiamo vedere all'opera nello script fill.fx.

Sono anche disponibili alcuni stili per la visualizzazione delle linee, definite nel package javafx.scene.shape e visibili con un esempio nello script line.fx.

Per la gestione del testo si veda lo script text.fx, che mostra come formattare stringhe di caratteri con diversi stili.

Per vedere come applicare trasformazioni a immagini c'é lo script transform.fx, nota che si fa riferimento a un file, strawberry.jpg che deve essere presente nella stessa directory dello script.

Per avere un esempio di come gestire il layout ci sono due script, hbox.fx e vbox.fx, che usano classi del package javafx.scene.layout.

Introduzione alle GUI - cellulare o desktop

Secondo tutorial per javaFX disponibile sul sito della sun. Mentre il primo era rivolto al core delle funzionalità di javaFX, qui vedremo meglio come costruire applicazioni con una GUI. La prima lezione ci serve per avere una introduzione alla materia.

javaFX é pensato per lavorare sia su un computer propriamente detto, con svariati pollici di spazio a disposizione per visualizzare il proprio output, sia su periferiche mobili, come a dire su telefoni cellulari dove lo spazio a disposizione é molto più modesto.

Ci sono due profili javaFX a disposizione, il common profile che include classi per operare in entrambi gli ambienti, e il desktop profile dedicato esclusivamente a funzionalità pensate per migliorare le applicazioni cosiddette desktop.

Per vedere l'effetto che fa un applicazione javaFX su un telefono, é possibile utilizzare l'emulatore incluso nel javaFX SDK. Se si sta usando netbeans, si può impostare nelle proprietà del progetto il modello di esecuzione in modo che l'eseguibile in fase di testing corra sul emulatore di cellulare (il tutto si trova nelle proprietà del progetto, sotto la categoria "run")

Come primo esempio per vedere come gira un applicazione javaFX é reso disponibile sul sito sun un semplice script, ball.fx, che mostra per l'appunto una palla.