package ca.nengo.ui.lib.world.elastic;
import java.awt.BasicStroke;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import ca.nengo.ui.lib.Style.NengoStyle;
import ca.nengo.ui.lib.world.Destroyable;
import ca.nengo.ui.lib.world.WorldLayer;
import ca.nengo.ui.lib.world.WorldObject.Listener;
import ca.nengo.ui.lib.world.WorldObject.Property;
import ca.nengo.ui.lib.world.piccolo.WorldObjectImpl;
import ca.nengo.ui.lib.world.piccolo.primitives.Path;
import ca.nengo.ui.lib.world.piccolo.primitives.PiccoloNodeInWorld;
public class ElasticObject extends WorldObjectImpl {
// Cache the Elastic world for fast access because it is used often
private ElasticGround elasticGround;
private boolean isAnchored = false;
private static final double DEFAULT_REPULSION_DISTANCE = 150;
private double repulsionRange;
/**
* Counts the number of times the position has been locked
*/
private int positionLock = 0;
public ElasticObject() {
super();
init();
}
public ElasticObject(PiccoloNodeInWorld node) {
super(node);
init();
}
public ElasticObject(String name) {
super(name);
init();
}
public ElasticObject(String name, PiccoloNodeInWorld node) {
super(name, node);
init();
}
private void init() {
addPropertyChangeListener(Property.PARENTS_CHANGED, new Listener() {
public void propertyChanged(Property event) {
if (getParent() instanceof ElasticGround) {
elasticGround = (ElasticGround) getParent();
}
}
});
addPropertyChangeListener(Property.BOUNDS_CHANGED, new Listener() {
public void propertyChanged(Property event) {
recalculateRepulsionRange();
}
});
recalculateRepulsionRange();
}
private void recalculateRepulsionRange() {
double range = DEFAULT_REPULSION_DISTANCE;
Rectangle2D bounds = localToGlobal(getBounds());
double radius = Math.sqrt(bounds.getWidth() * bounds.getWidth() + bounds.getHeight()
* bounds.getHeight());
range += radius;
this.repulsionRange = range;
}
protected ElasticGround getElasticWorld() {
return elasticGround;
}
@Override
public Point2D getOffset() {
if (getElasticWorld() != null) {
return getElasticWorld().getElasticPosition(this);
} else {
}
return super.getOffset();
}
/**
* This is the real getOffset function.
*
* @param x
* @param y
*/
public Point2D getOffsetReal() {
return super.getOffset();
}
public boolean isAnchored() {
return isAnchored;
}
public boolean isPositionLocked() {
if (elasticGround != null) {
return elasticGround.isPositionLocked(this);
}
return false;
}
private Anchor anchor;
public void setAnchored(boolean anchored) {
if (isAnchored != anchored) {
isAnchored = anchored;
if (isAnchored) {
if (anchor == null) {
anchor = new Anchor(this);
}
setPositionLocked(true);
} else {
if (anchor != null) {
anchor.destroy();
anchor = null;
}
setPositionLocked(false);
}
}
}
/**
* @see edu.umd.cs.piccolo.PNode#setOffset(double, double)
* <p>
* If NetworkViewer exists as a parent, this becomes a re-direct to
* Network Viewer's set location function.
* </p>
*/
@Override
public void setOffset(double x, double y) {
if (getElasticWorld() != null) {
getElasticWorld().setElasticPosition(this, x, y);
} else {
super.setOffset(x, y);
}
}
/**
* This is the real setOffset function.
*
* @param x
* @param y
*/
public void setOffsetReal(double x, double y) {
super.setOffset(x, y);
}
public void setPositionLocked(boolean lock) {
if (lock) {
positionLock++;
} else {
positionLock--;
if (positionLock < 0)
positionLock = 0;
}
if (elasticGround != null) {
if (positionLock > 0) {
elasticGround.setPositionLocked(this, true);
} else {
elasticGround.setPositionLocked(this, false);
}
}
}
@Override
public void setSelected(boolean isSelected) {
super.setSelected(isSelected);
setPositionLocked(isSelected);
}
@Override
protected void prepareForDestroy() {
if (anchor != null) {
anchor.destroy();
anchor = null;
}
super.prepareForDestroy();
}
public double getRepulsionRange() {
return repulsionRange;
}
}
class Anchor implements Destroyable, Listener {
private ElasticObject obj;
private Path border;
private Path line;
public Anchor(ElasticObject obj) {
super();
this.obj = obj;
WorldLayer ground = obj.getWorldLayer();
border = Path.createRectangle(0, 0, 1, 1);
line = new Path();
// line.setStrokePaint(Style.COLOR)
border.setPaint(null);
line.setPaint(null);
border.setStroke(new BasicStroke(2f));
line.setStroke(new BasicStroke(2f));
border.setStrokePaint(NengoStyle.COLOR_ANCHOR);
line.setStrokePaint(NengoStyle.COLOR_ANCHOR);
ground.addChild(line);
ground.addChild(border);
updateBounds();
obj.addPropertyChangeListener(Property.REMOVED_FROM_WORLD, this);
obj.addPropertyChangeListener(Property.GLOBAL_BOUNDS, this);
}
private boolean destroyed;
public void destroy() {
if (!destroyed) {
line.destroy();
border.destroy();
destroyed = true;
obj.removePropertyChangeListener(Property.REMOVED_FROM_WORLD, this);
obj.removePropertyChangeListener(Property.GLOBAL_BOUNDS, this);
}
}
private static int SIZE_ANCHOR = 15;
private void updateBounds() {
border.moveToFront();
line.moveToFront();
Rectangle2D bounds = obj.localToGlobal(obj.getBounds());
border.setBounds(bounds);
if (bounds.getWidth() > 0) {
ArrayList<Point2D> points = new ArrayList<Point2D>(4);
double x = bounds.getCenterX();
double y = bounds.getMaxY();
points.add(new Point2D.Double(x, y));
y += SIZE_ANCHOR * (2f / 3f);
points.add(new Point2D.Double(x, y));
x -= SIZE_ANCHOR;
points.add(new Point2D.Double(x, y));
x += SIZE_ANCHOR * 2;
points.add(new Point2D.Double(x, y));
line.setPathToPolyline(points.toArray(new Point2D[] {}));
}
}
public void propertyChanged(Property event) {
if (event == Property.REMOVED_FROM_WORLD) {
destroy();
} else if (event == Property.GLOBAL_BOUNDS) {
updateBounds();
}
}
}