sábado, 31 de enero de 2009

JavaFX: ¿Cuantas bolitas? (test de rendimiento gráfico)


Este ejercicio contiene 3 controles de barra que permiten ajustar:

1) El número de bolas rebotando en la ventana;
2) La transparencia de las bolas;
3) El radio de las bolas.



Cada nueva bola que se agrega, lo hace con un efecto (cambia el radio un par de veces) y luego continua rebotando entre los márgenes de la ventana. Si se aumenta el tamaño de la ventana, el area de desplazamiento aumenta, si se reduce, las bolas que se encuentran más alla del nuevo margen, regresan al centro de la ventana.

Un area de texto muestra el número de bolas en la ventana, el porcentaje de transparencia y el radio de los círculos.

Una instrucción importante es "canSkip:true", en el Timeline que actualiza la posicion de las bolas (y que por lo tanto, las redibuja). Esta instrucción evita que la CPU se sobrecargue de trabajo y solo realiza la actualización de las bolas si hay capacidad de procesamiento disponible. Intenten con unas 200 bolas y quiten el canSkip y veran como el programa se "pega".

Archivo Maix.fx
package howmany;

import javafx.scene.*;
import javafx.stage.Stage;
import javafx.ext.swing.SwingSlider;
import javafx.scene.layout.VBox;
import javafx.scene.text.*;

// si cambia tamaño de la ventana
// reposiciona los circulos mas alla del margen
var maxX=250.0 on replace { circles.resetPos() };
var maxY=250.0 on replace { circles.resetPos() };

// seleccion numero de bolas
var selector = SwingSlider {
minimum: 0
maximum: 100
value: 0
vertical: false
};

// transparencia de las bolas
var transparency = SwingSlider {
minimum: 0
maximum: 100
value: 50
vertical: false
};

// radio de las bolas
var radius = SwingSlider {
minimum: 0
maximum: 40
value: 20
vertical: false
};

// texto para desplegar valores
var total = Text {
font: Font { size: 18 }
textOrigin:TextOrigin.TOP;
};

// componente que contiene los circulos
var circles = element {
balls:bind selector.value;
radius:bind radius.value;
level:bind transparency.value / 100.0;
display: total
maxX:bind maxX
maxY:bind maxY
};

Stage {
// si cambia tamaño de la ventana
// actualiza variables con dimensiones
width: bind maxX with inverse
height: bind maxY with inverse

scene: Scene { content: [
circles,
VBox { content: [
selector,
transparency,
radius,
total ]
} ]
}
}


Archivo element.fx

package howmany;

import javafx.stage.Stage;
import javafx.scene.*;
import javafx.scene.text.*;
import javafx.scene.shape.Circle;
import javafx.scene.paint.Color;
import javafx.animation.*;
import java.lang.Math;


// clase que extiende circulos
class ball extends Circle {

// "sobreescribe" los parametros del circulo normal,
// de manera de tener los valores en la nueva clase
override var stroke = null;
override var fill = Color.RED;
override var centerX = 100;
override var centerY = 100;
override var radius = 10;
override var scaleX;
override var scaleY;

public var incx;
public var incy;

// mueve el circulo en los incrementos de la instancia
function move():Void {
centerX += incx;
centerY += incy;
if (centerX<0 or centerX>maxX) then
incx = -incx;
if (centerY<0 or centerY>maxY) then
incy = -incy;
};

// efecto para cuando crea el circulo
// cambia un par de veces de tamaño (como rebote)
var dropIn = Timeline {
repeatCount:5
autoReverse:true
keyFrames: [
at (0s) { scaleX => 2.0; scaleY => 2.0 }
at (0.3s) { scaleX => 1.0; scaleY => 1.0 }
]
};

// al crear el circulo, dispara el efecto de rebote
postinit {
dropIn.play();
};

};


// clase que contiene a todos los circulos
public class element extends CustomNode {

public var balls = 50.0;
public var display = Text { };
public var maxX;
public var maxY;
// si cambia la transparencia
public var level = 1.0 on replace {
// actualiza la opacidad de todos los circulos
for (obj in objs) obj.opacity = level; };
// si cambia el radio
public var radius = 10.0 on replace {
// actualiza el radio de todos los circulos
for (obj in objs) obj.radius = radius; };

// arreglo que contiene todos los circulos
var objs:ball[];
// colores que asigna a los circulos
var colors:Color[]=[
Color.RED, Color.BLUE, Color.YELLOW,
Color.PURPLE, Color.CYAN];

var i=0;

// agrega nuevo circulo al componente
function addBall():Void {
var ix=Math.random()*15 - 7;
var iy=Math.random()*15 - 7;
var circ = ball {
centerX:maxX/2
centerY:maxY/2
incx: ix
incy:iy
radius:radius
fill: colors[i mod sizeof colors]
opacity:level
};
insert circ into objs;
};


// crea el componente con los circulos
override function create():Node {

// chequea si debe agregar o quitar circulos
Timeline {
repeatCount:Timeline.INDEFINITE
keyFrames: [
KeyFrame {
time: 0.2s
action: function():Void {
display.content=
"{sizeof objs} / {(level*100) as Integer}% / {radius}";
if (sizeof objs < balls) then {
addBall();
i++;
}
if (sizeof objs > balls) then
delete objs[0];
}
}
]
}.play();

// mueve los circulos
Timeline {
repeatCount:Timeline.INDEFINITE
keyFrames: [
KeyFrame {
time: 0.04s
// para evitar que se sobrecargue de trabajo
canSkip: true
action: function():Void {
for (obj in objs)
obj.move();
}
}
]
}.play();

// retorna el componente con los circulos
Group {
content: bind objs
};

};

// reposicion los circulos si excende el margen
public function resetPos():Void {
for (obj in objs) {
var pos = obj.boundsInScene;
if (pos.maxX > maxX) then
obj.centerX = maxX/2 ;
if (pos.maxY > maxY) then
obj.centerY = maxY/2 ;
}
};

}

miércoles, 28 de enero de 2009

Mi primer juego en JavaFX: Alunizaje


Al juego le falta un par de detalles, como que la nave aparezca inicialmente en cualquier area de la pantalla. Por ahora aparece al centro a una velocidad y dirección aleatoria. También me falta agregar que la "base" de alunizaje tenga una posición aleatoria y que detecte cuando aterriza fuera de ella. Son detalles menores que quizas hasta puedes terminar tú.



El juego usa 4 teclas. La barra de espacio, para encender el motor principal. Las teclas de flecha hacia izquierda y derecha, que encienden los motos auxiliares para inclinar el módulo, y la tecla con la flecha hacia arriba, que enciende el piloto automático, para frenar la rotación del módulo (automáticamente).

Está bastante realista, los retrocohetes se encienden cuando corresponde y dejan de operar cuando no hay mas combustible. También incluye alarmas visuales y sonoras para cuando se excede la velocidad de aterrizaje, la inclinación del módulo es incorrecta cuando esta cerca de alunizar o cuando el combustible baja de 1/3 del estanque.

Me resultó entretenido hacer este juego y también descubrir LO INCREIBLEMENTE SIMPLE que es desarrollar cosas bastante decentes en tan poco tiempo (el grueso estaba operando en un par de horas... ¡y eso que estoy aprendiendo JavaFX script!).

La facilidad de incluir gráficos desde Adobe Illustrator sin duda es una ventaja inmensa. Especialmente, porque puede ser reemplazada por el trabajo de un diseñador más experto, sin necesidad de tocar el código de JavaFX script ya desarrollado. De hecho, con poco trabajo adicional podría agregarse multiples "skins" o apariencias al juego... escenarios de distintos planetas y naves por ejemplo (pudiendo relocalizar las alarmas, botones, formas de la nave, motores, entorno, etc).

El proyecto Netbeans 6.5 completo está disponible aquí (código fuente, gráfica, sonidos, etc).

El código requiere que esté incluida la libreria JavaFX FXD 1.0 en el proyecto.

lunes, 26 de enero de 2009

JavaFX: Objetos que se devuelven a su origen si no caen en el destino


Esta es una versión modificada del ejercicio anterior. Si se arrastra un objeto y este no cae dentro del destino, entonces rebota hacia el punto de origen.



Lo único que agregué fue que al crear el objeto guarda su posicion inicial en las variables original_x y original_y, y si detecta que no cae dentro del destino, ejecuta un Timeline que lo hace volver a su posición de origen.

Adicionalmente, hice algunos pequeños cambios para reemplazar el destino circular por un cuadrado de esquinas redondeadas.

Aquí hay una copia completa del proyecto Netbeans 6.5 (Download Complete Project)

JavaFX: Objetos que son atraídos por su área de destino


Hola nuevamente, aquí va otro ejercicio entretenido e interesante. Probablemente se puede mejorar, pero por ahora, esta es la manera en que lo hice funcionar.



En parte, se basa en el código de ejemplos anteriores. Por ejemplo, el Interpolador de resorte es el mismo código que había usado antes. La clase DraggableObj es una versión modificada de un ejemplo anterior. Hay dos pequeñas clases que sólo describen un par de objetos simples y la clase principal que tiene un par de cosillas interesantes.

Por ejemplo, la clase principal tiene una lista de objetos para arrastrar, que sólo son "descritos" y que luego el programa transforma en los objetos gráficos definitivos, que el usuario manipula. También utiliza una función (toPaint) para consolidar todos los elementos gráficos que debe pintar y manipular.

Respecto a los ajustes que hace en DraggableObj al detectar que el objeto se acercó al area de destino: Lo que ocurre es que los círculos son posicionados por el centro, mientras los rectángulos/cuadrados son posicionados por la esquina superior izquierda (creo que hay una forma de mover ese punto, pero no lo he probado ni me consta que realmente exista... sólo tengo la impresión que leí algo como eso).

Aquí hay una copia del proyecto Netbeans 6.5 completo. (Download Complete Project)

Aquí va el código:

archivo Main.fx

package placeobject;

import javafx.stage.Stage;
import javafx.scene.*;
import javafx.scene.text.*;
import javafx.scene.shape.*;
import javafx.scene.paint.*;
import javafx.scene.Node;

var destinationMessage:Text;

// area de destino
var destinationArea=Circle{
opacity: 0.5
centerX: Area.destination.x
centerY: Area.destination.y
radius: Area.destination.radius
fill: Area.destination.color
};

// mensaje para destino
destinationMessage=Text {
font: Font { size: 24 }
x: bind messagePosX, y: bind messagePosY
content: "Destino"
textOrigin:TextOrigin.TOP
}

// calcula posicion para mensaje
var messagePosX = Area.destination.x - destinationMessage.boundsInLocal.width/2;
var messagePosY = Area.destination.y - destinationMessage.boundsInLocal.height/2;


// lista de objetos por arrastrar
var objects= [
MyObject {type:"square", size:100, x:50, y:50, color:Color.BLUE},
MyObject {type:"circle", size:100, x:250, y:50, color:Color.RED},
MyObject {type:"square", size:80, x:50, y:250, color:Color.PURPLE},
MyObject {type:"circle", size:120, x:200, y:200, color:Color.YELLOW}
];

// carga objetos en una secuencia (array)
var list:Node[];
for (obj in objects) {

// si es un cuadrado
if (obj.type=="square") then {
var rect=DraggableObj{
translateX:obj.x
translateY:obj.y
content: Rectangle{
width:obj.size
height:obj.size
fill:obj.color
}
};
insert rect as Node into list;
}

// si es un circulo
if (obj.type=="circle") then {
var circ=DraggableObj{
translateX: obj.x + obj.size/2
translateY: obj.y + obj.size/2
isCircle: true
content: Circle{
radius: obj.size/2
fill:obj.color
}
}
insert circ as Node into list;
}

};

// obtiene los objetos a pintar
function toPaint():Node[] {
// array para objetos
var objs:Node[];
// crea area de destino
var dest=Group {
content: [
destinationArea,
destinationMessage
]
};
// agrega destino a la lista
insert dest into objs;
// agrega los objetos a la lista
insert list into objs;
// retorna un array de objetos por pintar
return objs;
};

var stage=Stage {
title: "Application title"
width: 600
height: 600
scene: Scene {
content:
bind toPaint()
}
};


archivo DraggableObj.fx


package placeobject;

import javafx.scene.*;
import javafx.scene.shape.*;
import javafx.scene.paint.*;
import javafx.animation.Timeline;
import javafx.animation.Timeline.*;
import javafx.animation.Interpolator;
import javafx.animation.Interpolator.*;
import javafx.geometry.*;


def spring = SpringInterpolator { bounce: true};

public class DraggableObj extends CustomNode {

public var content: Node[];
public var isCircle = false;

var endX = 0.0;
var endY = 0.0;
var startX = 0.0;
var startY = 0.0;

var op:Number=100;
var scale:Number=100;
var final:Point2D;
var origin:Point2D;
var original_width=0.0;
var original_height=0.0;

// vuelve escala a 100%
var s=Timeline {
keyFrames: [
at (1.5s) { scale => 100.0 tween spring},
]
};

// aumenta escala de 100% a 150%
var i=Timeline {
keyFrames: [
at (0s) { scale => 100.0 },
at (0.3s) { scale => 150.0},
]
};

// desplazamiento hacia el centro de destino
var dxy=Timeline {
keyFrames: [
at (0s) { endX => origin.x; endY => origin.y },
at (1s) { endX => final.x tween spring; endY => final.y tween spring},
]
};


override function create() : Node {

// crea objeto
var newNode = Group {

translateX: bind endX
translateY: bind endY
content: bind content
opacity: bind op/100.0
scaleX: bind scale/100.0
scaleY: bind scale/100.0

onMousePressed: function(e) {
startX = e.dragX-endX;
startY = e.dragY-endY;
dxy.stop();
s.stop();
i.playFromStart();
}
onMouseDragged: function(e) {
endX = e.dragX-startX;
endY = e.dragY-startY;
}
onMouseReleased: function(e) {
s.time=0s;
s.play();
checkDestination();
}
};

// obtiene tamaño del objeto
original_width=newNode.boundsInLocal.width;
original_height=newNode.boundsInLocal.height;

// retorna el objeto creado (la instruccion return es opcional)
return newNode;
}


// chequea si esta proximo al destino
function checkDestination() {

// obtiene coordenadas y tamaño del destino
var dest=Rectangle2D {
minX:Area.destination.x - Area.destination.radius
minY:Area.destination.y - Area.destination.radius
width:Area.destination.radius*2
height:Area.destination.radius*2
};

// verifica si el area del objeto arrastrado y el destino coinciden
if (dest.intersects(this.boundsInScene)) then {
// punto de origen para la animacion (posicion actual del objeto)
origin=Point2D{x:endX, y:endY};
// cuanto debe moverse el objeto para alcanzar el destino
final=this.sceneToLocal(Area.destination.x, Area.destination.y);
// si no es un circulo (y el centro esta desplazado a la esquina
// superior izquierda del rectangulo/cuadrado)

if (not isCircle) {
// ajusta cuanto debe moverse
final=Point2D {
// menos la mitad del ancho y alto
x: final.x - original_width / 2;
y: final.y - original_height / 2;
};
};
// inicia animacion para alcanzar el centro del destino
dxy.playFromStart();
};

}

}


archivo MyObject.fx


package placeobject;

import javafx.scene.paint.Color;

public class MyObject {
public var type:String;
public var size:Number;
public var x:Number;
public var y:Number;
public var color:Color;
}




archivo Area.fx


package placeobject;

import javafx.scene.paint.Color;

public class Area {
public var x:Number;
public var y:Number;
public var radius:Number;
public var color:Color;
}

public var destination=Area {
x:400
y:400
radius:100
color:Color.RED
};



sábado, 24 de enero de 2009

JavaFX: Applet con area para arrastrarlo


Esta versión posee un area delimitada que permite arrastrar el applet fuera del navegador. La versión anterior permitía arrastrar el applet desde cualquier area del applet. El inconveniente de la versión anterior, es que no permite utilizar drag and drop (arrastrar elementos) dentro del applet, porque arrastraba el applet completo fuera del navegador.




package advdragapplet;

import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.text.*;
import javafx.scene.text.TextOrigin;
import javafx.scene.shape.*;
import javafx.scene.paint.*;
import javafx.scene.input.MouseEvent;
import javafx.animation.Timeline;
import javafx.stage.AppletStageExtension;

// area para arrastrar el applet
var dragArea:Group;
dragArea = Group {

opacity: 0.5
// la barra solo aparece si es un applet y puede ser arrastrado
visible: bind AppletStageExtension.appletDragSupported
onMouseDragged:function(e:MouseEvent):Void {
stage.x += e.dragX;
stage.y += e.dragY;
}
content: [
Rectangle {
x: 0
y: 0
width: 140
height: 20
fill: Color.YELLOW
},
Text {
font: Font {
size: 12
}
x: 10,
y: 5
textOrigin:TextOrigin.TOP
content: "Arrastrame / Drag me"
}
]
};

var x:Number;
Timeline {
repeatCount: Timeline.INDEFINITE
autoReverse: true
keyFrames: [
at(0.0s) { x => 40}
at(0.5s) { x => 224}
]
}.play();

var stage=Stage {
title: "Draggable Applet"
width: 280
height: 116
scene: Scene {
fill:Color.PURPLE
content: [
Circle {
centerX: bind x,
centerY: 40
radius: 40
fill: Color.CYAN
},
dragArea,
]
}
extensions: [
AppletStageExtension {
shouldDragStart: function(e): Boolean {
e.primaryButtonDown and dragArea.hover;
}
}
]
}

JavaFX: Applet Arrastrable


Una de las nuevas funcionalidades de Java 6 update 10 es la posibilidad de crear applets "arrastrables" (que el usuario puede "arrastrar" fuera de la página de su navegador, y convertir en una aplicación de escritorio). Esta funcionalidad también está disponible -con un mínimo de trabajo- para los applets JavaFX.



Lo único que tienes que tienes que agregar a tu código es:

import javafx.stage.AppletStageExtension;

Y posteriormente, incluir este trozo como atributo del Stage:

extensions: AppletStageExtension {
shouldDragStart: function(e: MouseEvent): Boolean {
e.primaryButtonDown;
}
}

Listo, los usuarios de tu applet ya pueden arrastrarlo al escritorio y crear un short-cut (automáticamente) para re-lanzar el applet, sin siquiera tener que abrir su navegador.

Nota: Para desinstalar desde Windows un applet convertido en aplicación de escritorio, tienes que ir al Panel de Control y desinstalarlo como cualquier otro programa.

Aquí está el código del ejemplo en video:


package draggableapplet;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.text.*;
import javafx.scene.text.Font;
import javafx.scene.input.MouseEvent;
import javafx.animation.Timeline;
import javafx.scene.shape.Circle;
import javafx.scene.paint.Color;
import javafx.stage.AppletStageExtension;

var x:Number;

Timeline {
repeatCount: Timeline.INDEFINITE
autoReverse: true
keyFrames: [
at(0.0s) { x => 40}
at(1.0s) { x => 224}
]
}.play();

Stage {

title: "Application title"
width: 270
height: 116

extensions: AppletStageExtension {
shouldDragStart: function(e: MouseEvent): Boolean {
e.primaryButtonDown;
}
}

scene: Scene {
fill:Color.YELLOW
content: [
Circle {
centerX: bind x,
centerY: 40
radius: 40
fill: Color.RED
},
Text {
font: Font {
size: 24
}
x: 10,
y: 28
textOrigin:TextOrigin.TOP
content: "Arrastrame / Drag me"
}
]
}

}

miércoles, 21 de enero de 2009

JavaFX: Integración con recursos Adobe Illustrator


JavaFX permite integrar fácilmente recursos gráficos en el código, permitiendo dar una apariencia más rica y atractiva a las aplicaciones y juegos desarrollados con JavaFX.



Para traspasar recursos de Adobe Illustrator a JavaFX, tienes que instalar el JavaFX Production Suite.

Una copia completa del proyecto Netbeans -con los recursos Adobe Illustrator- está aquí ... (Download Complete Project)


archivo CDartUI.fx


package animatecd;

import javafx.scene.Node;
import javafx.fxd.UiStub;
import javafx.scene.Group;

public class CDartUI extends UiStub {

override public var url = "{__DIR__}CDart.fxz";
public var Artwork: Node;

override protected function update() {

Artwork=getNode("Artwork");
}

}

Archivo Main.fx

package animatecd;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
import javafx.scene.Group;
import javafx.animation.Timeline;

// carga imagenes de Illustrator
var ui = CDartUI {};
var scale:Number;

Timeline {
repeatCount: Timeline.INDEFINITE
autoReverse: true
keyFrames: [
at(0s) { scale => 0.1 }
at(10s) { scale => 12.0 }
]
}.play();

Stage {
width: 400
height: 400
scene: Scene {
content: Group {
content:ui
scaleX:bind scale
scaleY:bind scale
translateX:bind scale*80
translateY:bind scale*80
rotate:bind scale*36
}
}
}

{__DIR__} indica que el archivo CDart.fxz está en el mismo directorio que las clases compiladas (basta poner el archivo CDart.fxz en el directorio que contiene el código fuente para que Netbeans lo copie al directorio correspondiente con la copia para distribuir).

Si el archivo de Adobe Illustrator contiene mas de un elemento (layers identificados con la etiqueda "jfx:" y el nombre del elemento), puede acceder a las propiedades usando (en este caso) ui.Artwork.nombre_de_la_propiedad. Esto le permitirá mover los elementos contenido en el archivo Adobe, de manera independiente. Otras propiedades que puede alterar son... cambiar su opacidad (transparencia), rotarlo, escalarlo, etc.

JavaFX: Pintando un array (arreglo) de funciones y detectando el mouse encima


En este ejercicio es posible descubrir varias cosas interesantes:

1) Como se puede crear un arreglo cuyos elementos son funciones que retornan figuras gráficas.

2) Como se pueden consolidar en una sola figura los resultados de las funciones en el arreglo (bind for....)

3) Como, sin hacer nada adicional, el programa es capaz de detectar cuando el mouse entra y sale de esta figura consolidada.

4) Finalmente, como detalle gráfico, muestra como se puede aplicar un efecto de sombra sobre la figura consolidada.


Bastante para tan poco código, ¿no?





package experimentfunctions;

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

var count:Integer=0;

function circulo():Circle {
Circle{
radius:90
fill:Color.RED
}
};

function rectangulo():Rectangle {
Rectangle{
width:100
height:75
fill:Color.BLUE
}
};

function texto():Text {
Text{
translateX: -80
translateY: -70
font: Font{
name:"Verdana"
embolden:true;
size:24
}
content: "Prueba de texto"
}
};

var functions=[
circulo(),
rectangulo(),
texto()
];

var shadow=0;

var shadowAnim = Timeline {
repeatCount: Timeline.INDEFINITE
autoReverse: true
keyFrames: [
at(0s) { shadow => 4 }
at(0.3s) { shadow => 10}
]
};

Stage {
title: "Application title"
width: 250
height: 250
scene: Scene {
content: Group {
translateX:100
translateY:100
content: bind for (shape in functions) shape;
effect: DropShadow {
offsetY: bind shadow
offsetX: bind shadow
}
onMouseEntered: function(e) {
shadowAnim.stop();
}
onMouseExited: function(e) {
shadowAnim.play();
}
}
}
}

shadowAnim.play();

Una explicación más detallada sobre bind for (shape in functions) shape.

Lo que hace esta instrucción es... obtener cada una de las funciones en el arreglo:

for (shape in functions)

... luego ejecutarlas y pasar ese resultado como parte del contenido a pintar.

Una forma más ordenada de presentar el código sería:

content:
bind
for (shape in functions)
shape; <-- ejecuta y retorna la figura

lunes, 19 de enero de 2009

JavaFX: Limita el tamaño de la ventana (mínimo y máximo) - Bind with inverse


Esta es una variación del ejemplo anterior, que detectaba el tamaño de la ventana para ajustar el contenido (cómo descubrir el tamaño de la ventana). En este caso, fuerza dimensiones mínimas y máximas para la ventana.



Como vimos en el ejemplo anterior, la instrucción 'bind with inverse' permite iniciar el tamaño de la ventana unas ciertas dimensiones (300x300 pixeles), pero luego, las variables 'w' y 'h' reciben las variaciones del tamaño de la ventana, a través del mismo 'bind with inverse'. Esto, sumado a la instrucción 'on replace' (al definir las variables para el tamaño de la ventana), permite gatillar la validación del tamaño de la ventana.... y forzar su tamaño dentro de límites al asignar nuevos valores para las variables 'w' y 'h'.

En otras palabras, las variables 'w' y 'h' se hacen BIDIRECCIONALES... pues al modificar su valor, se afecta el tamaño de la ventana.... y al modificar el tamaño de la ventana, se actualiza los valores de las variables, con las nuevas dimensiones.


package experimentsize;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.shape.*;
import javafx.scene.paint.*;
import javafx.geometry.Rectangle2D;

// borde horizontal de la ventana
var bh=16.0;
// borde vertical de la ventana (incluye titulo)
var bv=36.0;
// dimension minima de la ventana
var minSize=Rectangle2D{width:200 height:200};
// dimension maxima de la ventana
var maxSize=Rectangle2D{width:500 height:500};

// valida dimensiones de la ventana
function validateSize():Void {
if (w<minSize.width) then w=minSize.width;
if (h<minSize.height) then h=minSize.height;
if (w>maxSize.width) then w=maxSize.width;
if (h>maxSize.height) then h=maxSize.height;
println("ajusto ventana a {w} x {h}");
}

// tamaño inicial de la ventana
var w=300.0 on replace {validateSize()};
var h=300.0 on replace {validateSize()};

Stage {
title: "Ajusta a dimension de ventana"
width: bind w with inverse
height: bind h with inverse
scene: Scene {
content: {
Circle {
translateX: bind (w-bh)/2
translateY: bind (h-bv)/2
radius:bind if (w<h) then
(w-50)/2
else
(h-80)/2
fill:Color.RED
}
}
}
}

JavaFX: Bind with inverse. Ajusta tamaño de círculo a tamaño de la ventana


La instrucción BIND es una de las cosas interesantes de JavaFX Script. Esta permite declarar la DEPENDENCIA entre una variable y otros elementos del programa. Por ejemplo, si al declarar un circulo, se define que el color de este dependerá de una variable X, al cambiar el color asignado a X se produce la actualización automática del círculo en pantalla, sin necesidad de ordenar al programa que repinte.




Circle {
radius:20
fill:bind colorcirculo
}

Ahora bien, la instrucción BIND puede ir acompañada de la instrucción WITH INVERSE, por ejemplo:

var a=bind b with inverse;

Esto significa que cuando se cambie el valor de 'b' en algún lugar del programa, se asignará el nuevo valor de 'b' a 'a', pero también quiere decir que cuando se modifique el valor de 'a' también se traspasará el valor de 'a' a 'b'.

¿Qué utilidad puede tener esta instrucción? ... pues, por ejemplo, se puede usar para ajustar el tamaño de un círculo al tamaño de la ventana que lo contiene... o para conocer las medidas de la ventana y cuando se cambia su tamaño.

En el siguiente ejemplo, se usa 'bind with inverse' para asignar el tamaño inicial de la ventana (300x300 pixeles)... pero luego, cuando se ajusta manualmente el tamaño de la ventana, la misma instrucción 'bind with inverse' pasa de regreso el tamaño actualizado de la ventana a las variables 'h' y 'w' que originalmente definieron su tamaño (h=height=alto y w=width=ancho).


package experimentsize;

import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.scene.Scene;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
import javafx.scene.shape.*;
import javafx.scene.paint.*;
import java.lang.System;

// tamaño inicial de la ventana
var w=300.0 on replace {
println("ajusto ancho de la ventana a {w}");
};
var h=300.0 on replace {
println("ajusto alto de la ventana a {h}");
};

// borde horizontal de la ventana
var bh=16.0;
// borde vertical de la ventana (incluye titulo)
var bv=36.0;

Stage {
title: "Ajusta al tamaño de ventana"
width: bind w with inverse
height: bind h with inverse
scene: Scene {
content: {
Circle {
translateX: bind (w - bh) / 2
translateY: bind (h - bv) / 2
radius:bind
if (w < h) then (w - 50) / 2 else ( h - 80) / 2
fill:Color.RED
}
}
}
}

domingo, 18 de enero de 2009

JavaFX: Objetos arrastrables con "efectos"


Este ejemplo incluye 3 clases:

1) Una clase (SpringInterpolator) que define un nuevo tipo de "interpolador" que genera un movimiento "elástico" (de resorte)

2) Una clase (DraggableObj) que permite que los elementos asignados sean "arrastrables" (drag and drop).

3) La clase principal (Main) que implementa la aplicación y que agrega 4 elementos para arrastrar por la ventana.



archivo SpringInterpolator.fx

package DraggableClass;

import javafx.animation.SimpleInterpolator;
import java.lang.Math;

public class SpringInterpolator extends SimpleInterpolator {
// amplitud de la onda
// controla que tan lejos puede llegar el objeto desde su punto final
public-init var amplitude:Number = 1.0;
// determina el peso del objeto
// hace el movimiento de la onda mas grande y llega mas lejos
public-init var mass:Number = 0.058;
// rigidez del movimiento de la onda o resolte
// hace la onda mas corta o ceñida
public-init var stiffness:Number = 12.0;
// hace la onda de fase, de modo que el objeto
// no acabe en el lugar de descanso.
// esta variable generalmente no cambia
public-init var phase:Number = 0.0;

// si actua como un resorte normal o rebota
public-init var bounce:Boolean = false;

// usado para calculos internos
var pulsation:Number;

init {
this.pulsation = Math.sqrt(stiffness / mass);
}

// ecuacion del resorte
override public function curve(t: Number) : Number {
var t2 = -Math .cos(pulsation * t + phase + Math.PI) * (1 - t) * amplitude ;
// usa el valor absoluto de la distancia si rebota
if(bounce) {
return 1 - Math.abs(t2);
} else {
return 1 - t2;
}
}
}

archivo DraggableObj.fx

package DraggableClass;

import javafx.scene.Group;
import javafx.scene.CustomNode;
import javafx.scene.Node;
import javafx.scene.shape.*;
import javafx.scene.paint.*;
import javafx.animation.Timeline;
import javafx.animation.Timeline.*;
import javafx.animation.Interpolator;
import javafx.animation.Interpolator.*;


public class DraggableObj extends CustomNode {

public var content: Node[];

var endX = 0.0;
var endY = 0.0;
var startX = 0.0;
var startY = 0.0;

var op:Number=100;
var scale:Number=100;

// produce un parpadeo rapido al
// al soltar los objetos arrastrados
var t = Timeline {
repeatCount: 3
keyFrames: [
at (0s) { op => 100.0 },
at (0.05s) { op => 0.0 }
]
}

// instancia el interpolador de resorte
def spring = SpringInterpolator { bounce: true};

// escala objetos con efecto de resorte
var s = Timeline {
keyFrames: [
at (1.5s) { scale => 100.0 tween spring},
]
};

// escala objectos de tamano original
// a tamano 50% mas grande al hacer
// click sobre ellos
var i = Timeline {
keyFrames: [
at (0s) { scale => 100.0 },
at (0.3s) { scale => 150.0},
]
};


override function create() : Node {

// produce una variacion constante en la
// opacidad de los elementos arrastrables
Timeline {
repeatCount: Timeline.INDEFINITE
autoReverse: true
keyFrames: [
at (0s) { op => 70.0 },
at (0.3s) { op => 100.0 tween Interpolator.EASEBOTH}
]
}.play();

Group {
translateX: bind endX
translateY: bind endY
content: bind content
opacity: bind op/100.0
scaleX: bind scale/100.0
scaleY: bind scale/100.0

onMouseDragged: function(e) {
endX = e.dragX-startX;
endY = e.dragY-startY;
}
onMousePressed: function(e) {
startX = e.dragX-endX;
startY = e.dragY-endY;
s.stop();
i.playFromStart();
}
onMouseReleased: function(e) {
t.playFromStart();
s.time=0s;
s.play();
}
};
}

}

archivo Main.fx

package DraggableClass;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.image.*;
import javafx.scene.paint.*;
import javafx.scene.shape.*;
import javafx.scene.input.MouseEvent;


var newCircle=DraggableObj{
translateX:700
translateY:200
content: Circle {
radius: 100
fill: Color.RED
opacity:0.75
}
};

// este objeto incluye un ARRAY (arreglo) de figuras
var newCircle2=DraggableObj{
translateX:200
translateY:200
content: [
Circle {
opacity:0.5
fill:Color.BLUE
radius:70
},
Rectangle {
translateX:-50
translateY:-50
width:100
height:100
rotate:45
fill:Color.GREEN
opacity:0.5
}
]
};

var newImage=DraggableObj{
translateX:200
translateY:500
content: ImageView {
opacity:0.8
preserveRatio:true
fitWidth:200
image: Image {
// url: "file:/C:/Users/myuser/Pictures/2008a.png"
url: "http://www.google.com/intl/en_ALL/images/logo.gif"
}
}
}

var newRect=DraggableObj{
translateX:700
translateY:500
content: Rectangle {
width:100
height:100
rotate:45
fill:Color.PURPLE
opacity:0.5
}
};


Stage {
title: "Application title"
width: 1200
height: 750
scene: Scene {
// Incluye un ARRAY de objetos
content: [
newCircle,
newCircle2,
newImage,
newRect
]
}
}

sábado, 17 de enero de 2009

JavaFX: Clases de clases... y dos Scene alternadas


Este ejemplo crea una clase que hace rotar un círculo. Luego, crea una nueva clase que utiliza varias instancias de la primera clase para hacer rotar varios círculos dentro del nuevo objeto. Finalmente, crea varias instancias de este último tipo de objeto y los pone en dos Scene que luego alterna... es decir, es un Stage con dos Scene.




package experimentroam;

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

// clase basica que dibuja circulo moviendose formando un cuadrado
class myObject extends CustomNode {

var x:Number;
var y:Number;

public var ix=0;
public var iy=0;
public var dx=100;
public var dy=100;
public var radius=40;
public var color=Color.RED;

var roamingtimeline = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: [
at(0s) { x => ix+radius; y => iy+radius }
at(0.3s) { x => dx-radius; y => iy+radius }
at(0.6s) { x => dx-radius; y => dy-radius }
at(0.9s) { x => ix+radius; y => dy-radius }
at(1.2s) { x => ix+radius; y => iy+radius }
]
};

// mensaje desplegado (compuesto por texto y rectangulo a su alrededor)
override protected function create():Node {
roamingtimeline.play();
Group {
content: [
Circle {
centerX: bind x
centerY: bind y
radius: bind radius
fill: bind color
}
]
}
};

};


// clase compuesta por objetos de la clase precedente
class myBigObject extends CustomNode {

var fig:myObject=myObject{
ix: 0
iy: 0
radius:55
dx:250
dy:250
color:Color.PURPLE
}
var fig2:myObject=myObject{
ix: 40
iy: 40
radius:40
dx:210
dy:210
color:Color.RED
}
var fig3:myObject=myObject{
ix: 80
iy: 80
radius:30
dx:170
dy:170
color:Color.GREEN
}
var fig4:myObject=myObject{
ix: 120
iy: 120
radius:20
dx:130
dy:130
color:Color.BLUE
}
var fig5:myObject=myObject{
ix: 160
iy: 160
radius:10
dx:90
dy:90
color:Color.YELLOW
}
var fig6:myObject=myObject{
ix: 190
iy: 190
radius:5
dx:60
dy:60
color:Color.ORANGE
}

override protected function create():Node {
Group {
content: [
fig,
fig2,
fig3,
fig4,
fig5,
fig6
]
}
};

}


// instancia de clase compuesta
var bigFig:myBigObject=myBigObject{};

// instancia de clase compuesta desplazada en la pantalla
var bigFig2:myBigObject=myBigObject{
translateX:250
translateY:0
};
// instancia de clase compuesta desplazada en la pantalla
var bigFig3:myBigObject=myBigObject{
translateX:250
translateY:250
};
// instancia de clase compuesta desplazada en la pantalla
var bigFig4:myBigObject=myBigObject{
translateX:0
translateY:250
};

var myScene:Scene=Scene {
fill:Color.BLACK
content: [
bigFig,
bigFig2,
bigFig3,
bigFig4
]
};

var myScene2:Scene=Scene {
fill:Color.BLACK
content: [
bigFig,
bigFig3,
]
};

var scenes=[myScene, myScene2];
var activeScene:Integer;

Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: [
at(0s) { activeScene => 0 }
at(1s) { activeScene => 1 }
]
}.play();

Stage {
title: "Application title"
width: 500+16
height: 500+36
scene: bind scenes[activeScene]
}

JavaFX: Textos que giran y cambian de tamaño


Este ejercicio lo desarrollé para crear una clase que contiene sus propios timeline y que modifica el tamaño del font mientras gira el mensaje. Quizas lo más interesante de este ejercicio es la manera en que se modifica el tamaño del font desde el timeline. Otra cosa interesante es cómo el programa obtiene las dimensiones del texto para pintar el area que lo contiene (para ello es necesario que el texto sea definido en una variable y luego incluida en el contenido).




package experimenttext;

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


class myObject extends CustomNode {

// texto en el mensaje
public var message = "Nada aun";
// color del mensaje
public var color = Color.BLACK;
// color borde
public var stroke = Color.BLACK;
// color fondo
public var fill = Color.WHITE;
// tamaño maximo del font
public var max_size = 64.0;

// angulo de la rotacion
var angle: Number;
// tamaño del font
var size: Number;
// font del texto
var varfont = function(size:Number):Font {
Font{
name:"Verdana"
oblique:true;
size:size;
}
};


// mensaje desplegado
var geomText = Text {
content: bind message
font : bind varfont(size)
fill: bind color
textOrigin: TextOrigin.TOP
};

// rotacion del mensaje (un giro en 2 segundos)
var rotatetimeline = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: [
at(0s) { angle => 0 }
at(2s) { angle => 360 }
]
};

// variacion del tamaño del font (de 12 a MAX_SIZE en 1 segundos)
var fontsize = Timeline {
repeatCount: Timeline.INDEFINITE
autoReverse: true
keyFrames: [
at(0s) { size => 12 }
at(0.3s) { size => max_size tween Interpolator.EASEBOTH}
]
};


// mensaje desplegado (compuesto por texto y rectangulo a su alrededor)
override protected function create():Node {

// inicia los timelines de la instancia
rotatetimeline.play();
fontsize.play();

Group {
content: [
// rectangulo
Rectangle {
width: bind geomText.boundsInLocal.width
height: bind geomText.boundsInLocal.height
stroke: bind stroke
fill: bind fill
},
// mensaje
geomText
]
// rotacion del conjunto (rect+mensaje)
rotate:bind angle
}
};

};

// primera instancia de mensaje
var msg=myObject{
translateX: 200
translateY: 150
message: "Primer mensaje"
};
// segunda instancia de mensaje
var msg2=myObject{
translateX: 300
translateY: 250
message: "Segundo mensaje"
color: Color.RED
stroke: Color.GREEN
fill: Color.YELLOW
max_size: 40.0
};
// tercera instancia de mensaje
var msg3=myObject{
translateX: 100
translateY: 350
message: "Tercer mensaje"
color: Color.WHITE
stroke: Color.BLUE
fill: Color.BLUE
max_size: 90.0
};

// aplicacion
Stage {
title: "Application title"
width: 800
height: 600
scene: Scene {
content: [
msg,
msg2,
msg3
]
}
}