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.
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 } ] } };
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.
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.
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:
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:
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
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?
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.
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).
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() };
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):
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.
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.
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.
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.
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).
// 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{
// 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 ] } }