lunes, 26 de enero de 2009

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
};



No hay comentarios: