martes, 24 de febrero de 2009

Una aplicación Desktop / Móvil que se adapta a la pantalla


Este ejemplo hace algo bastante simple, pero útil:

- Detecta el profile en que se está ejecutando (si es una aplicación móvil o de escritorio)
- Dependiendo de eso, obtiene las dimensiones de la pantalla (en caso de ser móvil) o define un tamaño inicial para la aplicación de escritorio.
- En caso de tratarse de una aplicación de escritorio, resta al tamaño de la ventana el borde de la misma, de manera de conocer el area útil (en la que puede desplegar información o gráficos).
- En el caso de una aplicación de escritorio, ajusta lo desplegado al tamaño de la ventana, en caso que se cambie el tamaño a la ventana.



Aquí está el código:

package mobilescreen;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.text.*;
import javafx.animation.*;
import javafx.scene.shape.Circle;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;

var dx: Number=2;
var dy: Number=3;
var x: Number;
var y: Number;
var realW:Number;
var realH:Number;

var profile = FX.getProperty("javafx.me.profiles");
var isMobile = profile != null and
profile.indexOf("MIDP") >= 0;

var w: Number on replace {
if (w == 0) then w = 300;
realW = w - (if (isMobile) 0 else 16);
x = realW / 2 ;
};

var h: Number on replace {
if (h == 0) then h = 400;
realH = h - (if (isMobile) 0 else 36);
y = realH / 2 ;
};

Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: [
KeyFrame {
time: 0.02s
action: function () {
x += dx;
y += dy;
if (x < 0 or x > realW )
dx = -dx;
if (y < 0 or y > realH)
dy = -dy;
}
}
]
}.play();

Stage {
width: bind w with inverse
height: bind h with inverse
scene: Scene {
content: [
Text {
font: Font { size: 16 }
x: 10, y: 30
content: bind
"width: {w}\nheight:{h}\nis mobile: {isMobile}"
},
Rectangle {
x: 10, y: 10
width: bind realW - 20
height: bind realH - 20
fill: Color.BLUE
opacity: 0.5
},
Circle {
centerX: bind x,
centerY: bind y
fill: Color.RED,
opacity: 0.5
radius: 40
}
]
}
};

lunes, 23 de febrero de 2009

Cómo crear una aplicación móvil con JavaFX y Netbeans 6.5


Este video muestra lo sencillo que es crear y probar aplicaciones móviles desarrolladas en JavaFX script con Netbeans 6.5.

Básicamente, sólo tienes que crear una aplicación y luego indicar que la vas a ejecutar con el emulador.

lunes, 16 de febrero de 2009

Como reemplazar y centrar textos incrustados en archivos Adobe Illustrator con JavaFX


Uno de los beneficios de crear gráfica para JavaFX con Adobe Illustrator, es que luego esta puede ser reemplazada sin necesidad de alterar el código. Esto permite cambiar rápidamente la apariencia de una aplicación o dotarla de "pieles" o "skins" (distintas apariencias, a elección del usuario). Estas plantillas, que permiten incluso cambiar la posición de los elementos en la interfaz de usuario, entre otras cosas, pueden contener textos, que son posibles de reemplazar (haciendo su contenido dinámico).

Pero, para hacerlo hay un par de detalles que es necesario conocer.

Primero crearé una plantilla con Adobe Illustrator:



Una vez generado el archivo .FXZ podemos analizar el código.

Sólo me concentraré en la manipulación de textos, ya que en artículos anteriores he mostrado como trabajar con otro tipo de elementos gráficos generados con Adobe Illustrator.

Primero, crearemos una clase que cargue el archivo gráfico (.fxz) y que obtenga de él los nodos con los objetos.


import javafx.scene.Node;
import javafx.fxd.UiStub;
import javafx.scene.text.*;

public class gaugeui extends UiStub {

override public var url = "{__DIR__}gauge.fxz";

public var reflex: Node;
public var needle: Node;
public var decoration: Node;
public var body: Node;

public var dispvalue: Text;
public-read var dispvaluew;
public-read var dispvaluex;

override protected function update() {

reflex = getNode("reflex");
needle = getNode("needle");
decoration = getNode("decoration");
body = getNode("body");

dispvalue = getNode("dispvalue") as Text;
dispvaluew = dispvalue.boundsInLocal.width;
dispvaluex = dispvalue.boundsInLocal.minX;

}
}

Revisemos el código:

override public var url = "{__DIR__}gauge.fxz";

Esa línea indica desde donde debe descargar el archivo con la gráfica: {__DIR__} apunta al mismo directorio que la aplicación;

public var reflex: Node;
public var needle: Node;
public var decoration: Node;
public var body: Node;

Declara algunas variables para contener otros elementos gráficos;

public var dispvalue: Text;
public-read var dispvaluew;
public-read var dispvaluex;

Declara una variable (dispvalue) de tipo TEXTO, para obtener el nodo que contiene el texto que vamos a reemplazar en este ejercicio, y dos variables adicionales asociadas a ese texto: una (dispvaluew) que contendrá el ancho del area reservada originalmente para el texto y otra (dispvaluex) que contendrá la posición horizontal original para el área de texto;

override protected function update() {

// obtiene los nodos graficos
reflex = getNode("reflex");
needle = getNode("needle");
decoration = getNode("decoration");
body = getNode("body");

// obtiene el nodo, pero lo transforma en texto
dispvalue = getNode("dispvalue") as Text;

// obtiene el ancho del texto original
dispvaluew = dispvalue.boundsInLocal.width;
// obtiene la posicion horizontal original del texto
dispvaluex = dispvalue.boundsInLocal.minX;

}


Para rescatar los nodos desde el archivo .fxz, utilizamos los nombres de asignamos a los elementos en el diseño. En nuestro ejemplo, llamamos al elemento de texto que deseamos manipular "jfx:dispvalue" (la identificacion jfx: permite identificar las etiquetas de nodos que importará JavaFX), y por lo tanto, obtenemos ese nodo con un getNode("dispvalue"), e inmediatamente lo convertimos en un Text (as Text).

Ahora bien, uno de los inconvenientes de la modificación de los textos desde un script, es que al modificar el contenido, se modifica el ancho del elemento que contiene el texto, y dependiendo de la alineación también puede modificarse la posición horizontal.

Por eso, al obtener el nodo de texto original, también almaceno la posición (dispvaluex) y el ancho (dispvaluew) que tenía el texto en el diseño. Con esta información más la nueva información de ancho generada al modificar el contenido del texto, puedo calcular la posición centrada del texto.

Para conseguirlo, puedo hacer algo como esto:

// instancia la clase que carga la gráfica (arriba)
var ui = gaugeui{};

// variable (degree) que mostrará en el area de texto
var degree = 0.0 on replace {

// actualiza contenido con numero entero convertido en string
ui.dispvalue.content="{degree as Integer}";

// obtiene nuevo ancho del texto (alterado por nuevo contenido)
var w = ui.dispvalue.boundsInLocal.width;

// calcula coordenada horizontal para mantener el texto
// centrado respecto a su posicion original en el diseño
ui.dispvalue.x = ui.dispvaluex +
ui.dispvaluew / 2 - w / 2;


};


Al modificarse degree, obtiene su valor entero y lo convierte en un string. Esto lo hace al incluir la variable como parte de una cadena de texto entre paréntesis de llave... "{expresión}"

A continuación, obtiene el nuevo ancho del texto modificado, con un: elemento_texto.boundsInLocal.width

Finalmente, obtiene la nueva posición horizontal del texto, usando la posición original del texto en el diseño mas la mitad del ancho del texto original. En este punto sabemos cual era el punto medio del texto original (posición respecto a la cual centraremos el nuevo texto).

A esa posición, restamos la mitad del nuevo ancho del texto.... y como resultado, el texto se mantiene visualmente centrado.



La inconveniente con el uso de TextAlignment.CENTER, es que el texto se centra respecto a las demás líneas de un párrafo (con más de una línea), entonces, si usamos un texto con una sóla línea, TextAligment.CENTER no tiene ningún efecto.

En resumen, hay que recuperar el nodo del texto por su nombre, convertilo y almacenarlo como Text, obtener el ancho y posición original del texto en el diseño... y calcular la posición horizontal del texto al modificar su contenido.

Por cierto, es posible exportar los archivos Illustrator a .fxz sin los fonts, pero si te quieres asegurar que los textos se mantendrán en apariencia, tamaño y posición, es mejor incluirlos. El resultado no es extremadamente grande, un font típico pesa cerca de 50KB.

Ojalá esto sea de utilidad.

domingo, 15 de febrero de 2009

Como hospedar Aplicaciones Java Web Start en Google App Engine


Estaba buscando donde hospedar un par de demos Java Web Start y finalmente recordé que tenía una cuenta de Google App Engine, así que me puse a investigar como hospedar allí los archivos JNLP y JAR de mis aplicaciones.

Hacerlo no resulta obvio, pues Google App Engine no ofrece un acceso de tipo FTP o un upload de archivos via web. La manera de hacerlo es sincronizando una aplicación Python local con los servidores de Google utilizando el utilitario appcfg.py update path/nombre_aplicacion_local, y la forma de subir los contenidos estáticos es incluirlos como parte de la aplicación Python y configurar la aplicación para servir contenidos estáticos e incluir una definición del MIME type para los archivos JNLP, de manera que sean bien interpretados por los navegadores (IE 7 despliega el contenido del archivo JNLP en vez de lanzar la aplicación, si el servidor no está configurado con el tipo JNLP entre los tipos servidos).

La solución final fue esta (creo que hay otras, pero esta fue la que me funcionó y no quise gastar más tiempo en investigar ;D):

- Creé dos subdirectorios como parte de una aplicación Python, en la ruta demos en mi copia local de Google App Engine... los llamé /exec y /jfx

- En el directorio /jfx copié los archivos JNLP

- En el directorio /exec copié los archivos JAR con mis aplicaciones JWS

- Luego, configuré el archivo app.yaml en el raíz de mi aplicación Python, de la siguiente manera:

application: javafxdemo
version: 3
runtime: python
api_version: 1

handlers:
- url: /exec
static_dir: exec

- url: /jfx
static_dir: jfx
mime_type: application/x-java-jnlp-file

- url: .*
script: javafxdemo.py

Finalmente, ejecuté el comando: appcfg.py update demos\javafxdemo ... que sincronizó el directorio local con la aplicación en Google App Engine.

Previamente modifiqué los archivos JNLP, de manera de explicitar que los archivos JNLP y los JAR están en directorios separados. Así que alteré las siguientes líneas:

<jnlp spec="1.0+" codebase=
"http://javafxdemo.appspot.com/"
href="jfx/Knob.jnlp">


...

<jar href="exec/Knob.jar" main="true"/>

Espero que les sirva este dato (por lo menos me servirá de ayuda-memoria la próxima)... o que me digan donde puedo hospedar (gratuítamente) contenidos estáticos sin tanto jaleo! jaja! y con 10GB de tráfico de salida gratuito ;D

Saludos.

Stage Controller, almost 1000 downloads - Casi 1000 visitas para descargar el Stage Controller


Almost 1000 people visited the blog in a day to download the Stage Controller class. Thanks to all of you for your interest.

La verdad me sorprendió, pero casi 1000 personas visitaron el blog en un día para descargar el componente que controla los Stage... :D ... Visitas desde Nigeria, China, India, EEUU, Brazil, etc. Se ve que JavaFX está consiguiendo una comunidad global de desarrolladores, ¿no?


Gracias a todos por su interés.

sábado, 14 de febrero de 2009

My Google Code: Knob & Stage Controller / Perilla y controlador de Stage


Two of the components I developed are now hosted at Google Code. They include documentation about how to use them.

Dos de los componentes que desarrollé están ahora hospedados en Google Code. Ellos incluyen la documentación sobre como usarlos.

Knob control / Perilla
http://code.google.com/p/javafxknob/
Java Web Start demo

Stage Controller / Controlador de Stage
http://code.google.com/p/javafxstagecontroller/
Java Web Start demo

JavaFX 1.1 liberado (incl. JavaFX Mobile)


Fue liberada la versión 1.1 de JavaFX, que incluye el primer release de JavaFX Mobile.

Ahora los programas desarrollados con JavaFX de escritorio corren sin modificación en JavaFX Mobile, a diferencia -por ejemplo- de Flex, que tiene su propia versión para móviles, FlexLite.

http://www.javafx.com/launch/


El SDK 1.1 incluye un emulador que permite probar que tal funciona la portabilidad escritorio -> móvil

También, a primera vista, se ven algunos detalles de mejor integracion con Netbeans 6.5, como algunos elementos en la paleta de componentes.

jueves, 12 de febrero de 2009

JavaFX: Stage "controller" demo - Demo "controlador" de Stage


This small code demostrates how easy is to handle the onResize and onMove events implemented by the Stage controller (Stage Controller).

Este pequeño código demuestra lo fácil que es manejar los eventos onResize y onMove implementados por el controlador de Stage (Controlador de Stage)



Here's the code / Aquí está el código:

var img = Image { url: "{__DIR__}myimage.jpg" };
var rect:Rectangle;
var sw:Integer;
var sh:Integer;

var cs = controlStage {
timeToCallFunctions:0.001s
timeToCheckPosSize:0.001s
initialPosition:controlStage.CENTER, persist:true
checkMinWidth:true, minWidth:270,
checkMinHeight:true, minHeight:270,
stickyBorders:true
onResize: function(e) { clip(e) }
onMove: function(e) { clip(e) }
};

// screen resolution
sw = cs.maxScrWidth;
sh = cs.maxScrHeight;

function clip(r:Rectangle2D) {
rect = Rectangle {
x: r.minX - (sw - img.width) / 2
y: r.minY - (sh - img.height) /2
width: r.width, height: r.height
}
};

Stage {
width: bind cs.width with inverse
height: bind cs.height with inverse
x: bind cs.x with inverse
y: bind cs.y with inverse
onClose: function() { cs.persistStage() };

scene: Scene { content:
ImageView {
translateX: bind -rect.x
translateY: bind -rect.y
image: img
clip: rect
}
}
};



Download the Netbeans 6.5 project, here. Descarga el proyecto Netbeans 6.5, aquí.

viernes, 6 de febrero de 2009

JavaFX: A Stage "controller" / Un "controlador" de Stage


Update: Stage Controller hosted on Google Code, includes documentation for using the class. Click Here for a Java Web Start example.



This class allows you to "attach" some automatic behaviors to your app window (Stage). For example, you can make the window to stick to the edges of the screen with a simple... stickyBorders:true ... or select the borders you want to make sticky... ie. stickyLeft:true

You can persist the position and/or size of the window (with a simple persist:true, persistPosition:true or persistSize:true). You can anchor the window to the borders too (ie. anchorBottom:true). Enforce minimum and maximum sizes or enforce a specific size for the window or keep it in the middle of the screen, etc.

It can trigger two events: onResize and onMove... so, your app can obtain it screen position and size at any moment (demo, handling events).

There are many options and combinations.

Enjoy it ;)

Actualización: Ahora el Controlador de Stage es capaz de mantener la posición y/o tamaño de la ventana de la aplicación entre sesiones.

Esta clase permite definir comportamientos automáticos para la ventana de tu aplicación. Por ejemplo, puedes hacer que la ventana se "pegue" a los bordes de la pantalla con un simple... stickyBorders:true ... o seleccionar los bordes que quieres que sean "pegajosos"... ej. stickyLeft:true

Puedes hacer que la aplicación recuerde su posición y/o tamaño entre sesiones de uso (con un simple persist:true, persistPosition:true o persistSize:true). Puedes "anclar" la ventana a los bordes también (ej. anchorBottom:true). Forzar tamaños mínimos y máximos o un tamaño específico para la ventana... o mantener la ventana en el centro de la pantalla.

También puede gatillar dos eventos: onResize (si cambia el tamaño de la ventana) y onMove (si cambia de posición la ventana en la pantalla)... de esa manera la aplicación puede saber -en todo momento- en que área de la pantalla está localizada o que tamaño tiene (demo, manejando los eventos).

Hay muchas opciones y combinaciones.

Ojalá sea útil ;)

How to use it / Cómo se usa...
/** Stage controller */
var cs = controlStage { //persistFile: "testStage.persist"
initialPosition:controlStage.RIGHT + controlStage.TOP, persist:true
checkMinWidth:true, minWidth:440, checkMinHeight:true, minHeight:270,
stickyBorders:true


onResize: function(e):Void {
println("resize to {e.width},{e.height} at {e.minX},{e.minY}");
}
onMove: function(e):Void {
println("moved to {e.minX},{e.minY} with {e.width},{e.height}")
};


};

Stage {
// bind Stage (window) to the controller
width: bind cs.width with inverse
height: bind cs.height with inverse
x: bind cs.x with inverse
y: bind cs.y with inverse


// on stage close, stores last position and size
onClose: function() { cs.persistStage() };

scene: Scene { ....





Descargar el proyecto Netbeans 6.5.... DOWNLOAD the Netbeans 6.5 project.... CLICK HERE

miércoles, 4 de febrero de 2009

Curso gratuito de JavaFX Script, de 15 semanas de duración


A partir del 30 de Enero pasado, se inició un curso gratuito por internet, para aprender JavaFX Script. Una vez por semana, los autores (Jim Weaver** & Sang Shin) publican el material necesario para experimentar y estudiar el lenguaje.

Solo tienen que mirar una vez por semana este sitio, al final del curso los participantes recibirán un diploma por su participación (aunque simbólico mas que nada, porque no es respaldado por ninguna institución):

JavaFX: un detalle importante sobre como se retornan valores


Si se han fijado, muchas veces nos encontramos con cosas como esta en el código de una aplicación JavaFX:

function figura():Node {
Group {
content: Line { startX:0,
startY:0, endX:100,
endY:100 }
}
};


Ahora, según la definición de la función, esta debe retornar un NODO (un objeto que puede ser una figura geométrica o una imagen, por ejemplo). Pero ese código, nunca ejecuta "explícitamente" un return.

Lo que sucede, es que JavaFX Script asume (salvo que exista explícitamente una instrucción RETURN), que la última expresión evaluada es lo que se retorna. En el caso del código (arriba), retornaría el Group.

El código escrito de manera más "formal" podría ser este:

function figura():Node {
var objeto = Group {
content: Line { startX:0,
startY:0, endX:100,
endY:100 }
};
return objeto;
};


Ahora, esta flexibilidad no deja de tener su encanto al poder por ejemplo escribir algo como:

var resultado:Number = if (a==b)
then { a+b/c } else { d/e+f };


... o poder decribir el cuerpo de una función como:

function calculo(a:Number,
b:Number):Number {

a + 1000 / b

};


En realidad, es un detalle que no deja de ser comodo y que hace más comprensible y breve el código... quizas más natural (siendo siempre opcional, y teniendo siempre la posibilidad de ser mas "formal" al codificar).

Ojalá esta explicación ayude a que comprendan mejor el código de algunos scripts.

JavaFX + Phys2D : JavaFX aprende sobre física (ojo, no solo ciencia)


Phys2D es una excelente API de física, que no solo sirve para simular descabellados experimentos de cientificos encerrados en sus laboratorios o estudiantes queriendo experimentar lo aprendido en clases, esta API es excelente para desarrollar JUEGOS. Si, porque con ella puedes hacer que los elementos del juego se comporten como elementos reales, que se deslicen por pendientes, reboten o se comporten de manera elástica, aceleren, frenen o colisionen.



Lo mejor es que es código abierto... para más información, visita el sitio web de Phys2D

Este ejemplo comprende tres archivos:

1) la biblioteca de Phys2D, que debe estar incluida en los libraries del proyecto (y que incluyo en el directorio lib).

2) una clase que describe EL MODELO de elementos en la simulación... en este caso, incluye una BASE (o anchor, ancla en inglés... que es el elemento fijo en la parte superior), un RESORTE, una masa y un trozo rigido que lo une a la segunda masa. En otras palabras, es un péndulo doble que cuelga por un resorte. OJO: Porque el modelo es una CLASE JAVA... así que de pasada pueden ver lo simple que es integrar Java + JavaFX Script.

3) la animación en JavaFX, que incluye un Timeline para generar los cálculos y el código que pinta los elementos dependiendo de las coordenadas recibidas desde el modelo físico. Bastante simple, como verán... pero no menos sofisticado.

Descarga el projecto Netbeans 6.5. DOWNLOAD the Netbeans project, here!

JavaFX: a Knob control - Un control "de perilla"


Update: Stage Controller hosted on Google Code, includes documentation for using the class. Click Here for a Java Web Start example.

English: Please, visit the Google Code hosted project

JavaFX me está convirtiendo realmente en una fábrica de componentes... este control lo desarrollé en menos de un día. Como verán es bastante sofisticado. No voy a entrar en detalles por ahora, pero el código del programa de ejemplo explota varias de sus funcionalidades.



Una de las cosas que dejo pendiente de demostrar, es cómo se pueden agregar nuevos tipos de perillas... basta con crear la gráfica en Adobe Illustrator y luego crear una clase que herede de la clase basicKnob (update: ver orangeKnob.fx). En esa nueva clase, pueden incluir detalles propios de la gráfica asociada... entre otras cosas, el nombre del archivo que contiene la grafica, las dimensiones de la nueva perilla (ancho y alto) y el radio inicial y final de las lineas del dial (la distancia al radio de la perilla), de manera que el componente pueda dibujar las lineas que le solicite el usuario y que estas lineas coincidan con la gráfica importada (update: ahora también incluye el radio inicial y final de las líneas cortas en el dial).

Aquí va el código fuente del componente, el programa demostrativo, los archivos de gráficos, etc. Sólo tienen que abrirlo en Netbeans 6.5 y ejecutarlo ;D

¡Ah! un detalle que me dió dolor de cabeza, hasta que recordé que lo había leído antes... si se fijan en el código, por cada control instancio la gráfica de la perilla ( ui: orangeKnob{} )... esto es así, porque si creo una sóla instancia y luego la enlazo desde varios lugares, solo queda activo el último enlace.... quizás debería ver si se puede crear algo como una versión "estática" con la gráfica... pero eso tengo que investigarlo y luego probar si funciona.

Descarga el proyecto completo de Netbeans 6.5... Download the complete Netbeans project... CLICK HERE.

lunes, 2 de febrero de 2009

JavaFX: Menú flotante (popup menu)


Este código utiliza un menú flotante que implemente yo. Es un trabajo en progreso, así que tiene un par de detalles pendientes... pero, por ahora, quizás le puede servir a alguien para su aplicación o para aprender como funciona...



Como he dicho, estoy aprendiendo JavaFX script, así que es probable que haga cosas de maneras inapropiadas, no se crean que soy un experto! ;) ... aun... en todo caso, implementar el menú flotante me tomó sólo un par de horas desde que comencé (de cero), para que vean lo productivo que puede ser este lenguaje.

Por cierto, se pueden crear menús flotantes sin tanta configuración, sólo son necesarias las opciones (el texto de la opción [text:] y la función a las que están asociadas [call:]), las demás configuraciones son opcionales... y ojo, que hay bastantes opciones por probar y combinar.... por cierto, JavaFX script permite hacer combinaciones como el Timeline que agregué para que cambie el color del borde del menú flotante. Quizás puedes experimentar agregándole un Interpolador de resorte para que se vea más simpático al aparecer o elegir una opción, o que el menú aparezca rotando cuando es lanzado, etc.

Aquí está el proyecto Netbeans 6.5 completo (Download the complete Netbeans project here, alpha version).

package popupmenu;

import javafx.stage.Stage;
import javafx.scene.*;
import javafx.scene.text.*;
import javafx.scene.paint.*;
import javafx.scene.shape.*;
import javafx.animation.*;
import javafx.scene.input.*;


var screenWidth=500.0;
var screenHeight=500.0;

// Primer menu flotante
var popupmenu=popupMenu{

corner:20
padding:8
borderWidth:4
borderColor:bind dynamicColor

// opciones del menu
content: [
menuItem { text:"Say Hello!", call: hello },
menuItem { text:"Again, say Hello!", call: again },

//menuSeparator { },
menuItem { text:"Say Bye!", call: bye }
]

};

// Timeline anima el borde del primer menu flotante
var dynamicColor:Color;
Timeline {
repeatCount:Timeline.INDEFINITE
autoReverse:true
keyFrames: [
at (0s) {dynamicColor => Color.YELLOW }
at (0.5s) {dynamicColor => Color.DARKBLUE}
at (1s) {dynamicColor => Color.LIGHTSALMON}
]
}.play();


// Segundo menu flotante
var popupCircle=popupMenu{

font: Font { size:20 };
gradient: LinearGradient { proportional: false
startX: 125.0, startY: 0.0, endX: 225.0, endY: 0.0
stops: [ Stop { offset: 0.0 color: Color.BLACK },
Stop { offset: 1.0 color: Color.RED } ] }
fill:Color.TRANSPARENT
stroke:Color.WHITE
highlight: Color.YELLOW
verticalSpacing:10

// opciones del menu
content: [
menuItem { text:"Multiline options\n"
"for your menu!", call: helloCircle },
menuItem { text:"This is a second\n"
"multiline option!", call: helloCircle },
]

};

// Tercer menu flotante
var popupmenu2=popupMenu{

font: Font { size: 18, name:"Verdana", embolden:true };
gradient: LinearGradient { proportional: true
stops: [ Stop { offset: 0.0 color: Color.GRAY },
Stop { offset: 1.0 color: Color.WHITE } ] }
fill:Color.TRANSPARENT
corner:20
padding:16
verticalSpacing:6
rotation:-25
highlight:Color.WHITE
highlightStroke:Color.RED

// opciones del menu
content: [
menuItem { text:"Say Hello!", call: hello },
menuItem { text:"Again, say Hello!", call: again },
menuItem { text:"Say Bye!", call: bye }
]

};


// funciones asignada a menus
function hello(e:MouseEvent):Void {
println("hello {e.x},{e.y}");
};

function helloCircle(e:MouseEvent):Void {
println("hello Circle {e.x},{e.y}");
};

function again(e:MouseEvent):Void {
println("hello, again {e.x},{e.y}");
};

function bye(e:MouseEvent):Void {
println("bye! {e.x},{e.y}");
};


Stage {
title: "Application title"
width: bind screenWidth with inverse
height: bind screenHeight with inverse
scene: Scene {
content: [
// agrega un fondo a la ventana
Rectangle {
fill: Color.BLUE
width: bind screenWidth
height: bind screenHeight
// le asigna un menu flotante
onMousePressed: function(e) {
popupmenu.event=e;
}
},
// crea un circulo
Circle {
// bloquea el click para que no dispare el menu flotante del fondo
blocksMouse:true
centerX: 100,
centerY: 100
radius: 40
fill: Color.RED
// le asigna un menu flotante
onMousePressed: function(e) {
popupCircle.event=e;
}
},
// crea un segundo circulo
Circle {
// bloquea el click para que no dispare el menu flotante del fondo
blocksMouse:true
centerX: 300,
centerY: 300
radius: 80
fill: Color.YELLOW
// le asigna un menu flotante
onMousePressed: function(e) {
popupmenu2.event=e;
}
},
// incluye los menus flotantes
popupmenu,
popupmenu2,
popupCircle
]
}
}