-package org.simantics.diagram.elements;\r
-\r
-import java.awt.BasicStroke;\r
-import java.awt.Cursor;\r
-import java.awt.Graphics2D;\r
-import java.awt.Shape;\r
-import java.awt.Stroke;\r
-import java.awt.geom.AffineTransform;\r
-import java.awt.geom.Point2D;\r
-import java.awt.geom.Rectangle2D;\r
-\r
-import org.simantics.g2d.canvas.impl.CanvasContext;\r
-import org.simantics.scenegraph.g2d.G2DNode;\r
-import org.simantics.scenegraph.g2d.events.EventTypes;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;\r
-import org.simantics.scenegraph.utils.NodeUtil;\r
-\r
-/**\r
- * Invisible resize node for resizing rectangular elements\r
- * \r
- * @author Teemu Lempinen\r
- *\r
- */\r
-public class ResizeNode extends G2DNode {\r
- \r
- private static final long serialVersionUID = 7997312998598071328L;\r
-\r
- \r
- /**\r
- * Interface for listeners listening resize events in nodes\r
- * @author Teemu Lempinen\r
- *\r
- */\r
- public interface ResizeListener {\r
- \r
- /**\r
- * Triggered when a node has been resized\r
- * @param newBounds new bounds for the node\r
- */\r
- public void elementResized(Rectangle2D newBounds, AffineTransform transform, boolean synchronizeToBackend);\r
- }\r
- \r
- /**\r
- * Enumeration for indicating which side of the resize bounds should affect translate\r
- * properties. \r
- * @author Teemu Lempinen\r
- *\r
- */\r
- public enum TranslateEdge {\r
- NONE, NORTH, SOUTH, EAST, WEST;\r
- }\r
- \r
- \r
- private boolean dragging = false;\r
- private ResizeListener resizeListener;\r
- private Rectangle2D bounds;\r
- private Stroke stroke;\r
- private TranslateEdge xTranslateEdge = TranslateEdge.WEST;\r
- private TranslateEdge YTranslateEdge = TranslateEdge.NORTH;\r
-\r
- int cursor = Cursor.DEFAULT_CURSOR;\r
- \r
- /**\r
- * Create a new Resize node with default border width (1)\r
- */\r
- public ResizeNode() {\r
- this(1);\r
- }\r
- \r
- /**\r
- * Create a new Resize node\r
- * \r
- * @param borderWidth Width of the border for handling mouse dragging\r
- */\r
- public ResizeNode(float borderWidth) {\r
- this.stroke = new BasicStroke(borderWidth);\r
- }\r
-\r
- @PropertySetter("Bounds")\r
- @SyncField("bounds")\r
- public void setBounds(Rectangle2D bounds) {\r
- assert(bounds != null);\r
- this.bounds = bounds;\r
- }\r
-\r
- @PropertySetter("stroke")\r
- @SyncField("stroke")\r
- public void setStroke(Stroke stroke) {\r
- this.stroke = stroke;\r
- }\r
-\r
- /**\r
- * Is dragging (resizing) active\r
- * @return\r
- */\r
- public boolean dragging() {\r
- return dragging;\r
- }\r
-\r
- /**\r
- * Set a ResizeListener for this node\r
- * @param listener ResizeListener\r
- */\r
- public void setResizeListener(ResizeListener listener) {\r
- this.resizeListener = listener;\r
- }\r
-\r
- @Override\r
- public void cleanup() {\r
- removeEventHandler(this);\r
- super.cleanup();\r
- }\r
-\r
- @Override\r
- public void init() {\r
- super.init();\r
- addEventHandler(this);\r
- }\r
-\r
- /**\r
- * Outline stroke shape of the bounds of this node\r
- * \r
- * @return Outline stroke shape of the bounds of this node\r
- */\r
- protected Shape getOutline() {\r
- return stroke.createStrokedShape(new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()));\r
- }\r
-\r
- /**\r
- * Dragging is started on mouse pressed, if mouse was pressed on the edge of bounds\r
- */\r
- @Override\r
- protected boolean mouseButtonPressed(MouseButtonPressedEvent event) {\r
- if(bounds != null && NodeUtil.isSelected(this, 1)) {\r
- // get mouse position\r
- Point2D local = controlToLocal( event.controlPosition );\r
- local = parentToLocal(local);\r
-\r
- // Get outline of this node\r
- Shape outline = getOutline();\r
-\r
- if (outline.contains(local)) {\r
- dragging = true;\r
- return true;\r
- }\r
- }\r
- return super.mouseButtonPressed(event);\r
- }\r
-\r
- /**\r
- * Get resize cursor for the location where the drag started.\r
- * \r
- * @param local Point of origin for drag\r
- * @return Cursor int\r
- */\r
- private int getCursorDirection(Point2D local) {\r
- float width = ((BasicStroke)stroke).getLineWidth();\r
-\r
- // Check the direction of the resize\r
- int cursor = 0;\r
- if (local.getX() >= bounds.getX() - width / 2 && \r
- local.getX() <= bounds.getX() + width / 2)\r
- // West side\r
- cursor = Cursor.W_RESIZE_CURSOR;\r
- else if (local.getX() >= bounds.getMaxX() - width / 2 && \r
- local.getX() <= bounds.getMaxX() + width / 2)\r
- // East size\r
- cursor = Cursor.E_RESIZE_CURSOR;\r
-\r
- if(local.getY() >= bounds.getY() - width / 2 && \r
- local.getY() <= bounds.getY() + width / 2) {\r
- // North side\r
- if(cursor == Cursor.W_RESIZE_CURSOR)\r
- cursor = Cursor.NW_RESIZE_CURSOR;\r
- else if(cursor == Cursor.E_RESIZE_CURSOR)\r
- cursor = Cursor.NE_RESIZE_CURSOR;\r
- else\r
- cursor = Cursor.N_RESIZE_CURSOR;\r
- } else if(local.getY() >= bounds.getMaxY() - width / 2 && \r
- local.getY() <= bounds.getMaxY() + width / 2) {\r
- // South side\r
- if(cursor == Cursor.W_RESIZE_CURSOR)\r
- cursor = Cursor.SW_RESIZE_CURSOR;\r
- else if(cursor == Cursor.E_RESIZE_CURSOR)\r
- cursor = Cursor.SE_RESIZE_CURSOR;\r
- else\r
- cursor = cursor | Cursor.S_RESIZE_CURSOR;\r
- }\r
- return cursor;\r
- }\r
-\r
- double dragTolerance = 0.5;\r
- \r
- @Override\r
- protected boolean mouseMoved(MouseMovedEvent e) {\r
- if(dragging) {\r
- // If dragging is active and mouse is moved enough, resize the element\r
- Point2D local = controlToLocal( e.controlPosition );\r
- local = parentToLocal(local);\r
-\r
- Rectangle2D bounds = getBoundsInLocal().getBounds2D();\r
-\r
- if(Math.abs(bounds.getMaxX() - local.getX()) > dragTolerance ||\r
- Math.abs(bounds.getMaxY() - local.getY()) > dragTolerance) {\r
- resize(local, false);\r
- }\r
- return true;\r
- } else if(NodeUtil.isSelected(this, 1)){\r
- // Dragging is not active. Change mouse cursor if entered or exited border\r
- Point2D local = controlToLocal( e.controlPosition );\r
- local = parentToLocal(local);\r
-\r
- Shape outline = getOutline();\r
- if (outline.contains(local)) {\r
- cursor = getCursorDirection(local);\r
- CanvasContext ctx = (CanvasContext)e.getContext();\r
- ctx.getMouseCursorContext().setCursor(e.mouseId, Cursor.getPredefinedCursor(cursor));\r
- } else if(cursor != 0) {\r
- cursor = 0;\r
- CanvasContext ctx = (CanvasContext)e.getContext();\r
- ctx.getMouseCursorContext().setCursor(e.mouseId, Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));\r
- }\r
-\r
- }\r
- return super.mouseMoved(e);\r
- }\r
-\r
- @Override\r
- protected boolean mouseDragged(MouseDragBegin e) {\r
- if(dragging) {\r
- return true; // Consume event\r
- } else {\r
- return false;\r
- }\r
- }\r
- \r
- private static double minSize = 5; // Minimum size for an element\r
-\r
- private void resize(Point2D local, boolean synchronize) {\r
- Rectangle2D bounds = getBoundsInLocal().getBounds2D();\r
- double x = bounds.getX();\r
- double y = bounds.getY();\r
- double w = bounds.getWidth();\r
- double h = bounds.getHeight();\r
- double dx = 0, dy = 0;\r
-\r
- /*\r
- * Resize to east and north:\r
- * \r
- * Move x and y coordinates and resize to get maxX and maxY to stay in their place\r
- */\r
- if(cursor == Cursor.W_RESIZE_CURSOR || cursor == Cursor.NW_RESIZE_CURSOR || cursor == Cursor.SW_RESIZE_CURSOR) {\r
- double dw = local.getX() - x;\r
- if(w - dw < minSize)\r
- dw = w - minSize;\r
- w = w - dw;\r
- \r
- \r
- if(TranslateEdge.WEST.equals(xTranslateEdge))\r
- dx = dw;\r
- } \r
- \r
- if(cursor == Cursor.N_RESIZE_CURSOR || cursor == Cursor.NW_RESIZE_CURSOR || cursor == Cursor.NE_RESIZE_CURSOR ) {\r
- double dh = local.getY() - y;\r
- if(h - dh < minSize)\r
- dh = h - minSize;\r
- h = h - dh;\r
- \r
- if(TranslateEdge.NORTH.equals(YTranslateEdge))\r
- dy = dh;\r
- } \r
- \r
- /*\r
- * Resize to west and south:\r
- * \r
- * Adjust width and height\r
- */\r
- if(cursor == Cursor.E_RESIZE_CURSOR || cursor == Cursor.NE_RESIZE_CURSOR || cursor == Cursor.SE_RESIZE_CURSOR) {\r
- double dw = local.getX() - bounds.getMaxX();\r
- w = w + dw > minSize ? w + dw : minSize;\r
- \r
- if(TranslateEdge.EAST.equals(xTranslateEdge))\r
- dx = w - bounds.getWidth();\r
- }\r
-\r
- if(cursor == Cursor.S_RESIZE_CURSOR || cursor == Cursor.SW_RESIZE_CURSOR || cursor == Cursor.SE_RESIZE_CURSOR) {\r
- double dh = local.getY() - bounds.getMaxY();\r
- h = h + dh > minSize ? h + dh : minSize;\r
-\r
- if(TranslateEdge.SOUTH.equals(YTranslateEdge))\r
- dy = h - bounds.getHeight();\r
- }\r
-\r
- /*\r
- * Set bounds and transform to the element before calling resize listener.\r
- * This prevents unwanted movement due to unsynchronized transform and bounds.\r
- */\r
- this.bounds.setRect(x, y, w, h);\r
-\r
- AffineTransform at = new AffineTransform();\r
- at.translate(dx, dy);\r
- if(resizeListener != null)\r
- resizeListener.elementResized(this.bounds, at, synchronize);\r
- }\r
-\r
- @Override\r
- protected boolean mouseButtonReleased(MouseButtonReleasedEvent e) {\r
- if(dragging) {\r
- // Stop resizing and set the size to its final state. \r
- Point2D local = controlToLocal( e.controlPosition );\r
- local = parentToLocal(local);\r
-\r
- resize(local, true);\r
- dragging = false;\r
-\r
- // Revert cursor to normal.\r
- CanvasContext ctx = (CanvasContext)e.getContext();\r
- cursor = Cursor.DEFAULT_CURSOR;\r
- ctx.getMouseCursorContext().setCursor(e.mouseId, Cursor.getPredefinedCursor(cursor));\r
- }\r
- return super.mouseButtonReleased(e);\r
- }\r
-\r
- @Override\r
- public int getEventMask() {\r
- return super.getEventMask() | EventTypes.MouseButtonPressedMask\r
- | EventTypes.MouseMovedMask\r
- | EventTypes.MouseButtonReleasedMask\r
- ;\r
- }\r
-\r
- @Override\r
- public Rectangle2D getBoundsInLocal() {\r
- if(bounds == null) return null;\r
- return bounds.getBounds2D();\r
- }\r
-\r
- @Override\r
- public void render(Graphics2D g2d) {\r
- // Do not draw anything\r
- }\r
- \r
- /**\r
- * Set which edge should affect X-translation \r
- * @param xTranslateEdge TranslateEdge.NONE, EAST or WEST\r
- */\r
- public void setxTranslateEdge(TranslateEdge xTranslateEdge) {\r
- this.xTranslateEdge = xTranslateEdge;\r
- }\r
- \r
- /**\r
- * Set which edge should affect Y-translation \r
- * @param yTranslateEdge TranslateEdge.NONE, SOUTH or NORTH\r
- */\r
- public void setYTranslateEdge(TranslateEdge yTranslateEdge) {\r
- YTranslateEdge = yTranslateEdge;\r
- }\r
-}\r
+package org.simantics.diagram.elements;
+
+import java.awt.BasicStroke;
+import java.awt.Cursor;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+
+import org.simantics.g2d.canvas.impl.CanvasContext;
+import org.simantics.scenegraph.g2d.G2DNode;
+import org.simantics.scenegraph.g2d.events.EventTypes;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
+import org.simantics.scenegraph.utils.NodeUtil;
+
+/**
+ * Invisible resize node for resizing rectangular elements
+ *
+ * @author Teemu Lempinen
+ *
+ */
+public class ResizeNode extends G2DNode {
+
+ private static final long serialVersionUID = 7997312998598071328L;
+
+
+ /**
+ * Interface for listeners listening resize events in nodes
+ * @author Teemu Lempinen
+ *
+ */
+ public interface ResizeListener {
+
+ /**
+ * Triggered when a node has been resized
+ * @param newBounds new bounds for the node
+ */
+ public void elementResized(Rectangle2D newBounds, AffineTransform transform, boolean synchronizeToBackend);
+ }
+
+ /**
+ * Enumeration for indicating which side of the resize bounds should affect translate
+ * properties.
+ * @author Teemu Lempinen
+ *
+ */
+ public enum TranslateEdge {
+ NONE, NORTH, SOUTH, EAST, WEST;
+ }
+
+
+ private boolean dragging = false;
+ private ResizeListener resizeListener;
+ private Rectangle2D bounds;
+ private Stroke stroke;
+ private TranslateEdge xTranslateEdge = TranslateEdge.WEST;
+ private TranslateEdge YTranslateEdge = TranslateEdge.NORTH;
+
+ int cursor = Cursor.DEFAULT_CURSOR;
+
+ /**
+ * Create a new Resize node with default border width (1)
+ */
+ public ResizeNode() {
+ this(1);
+ }
+
+ /**
+ * Create a new Resize node
+ *
+ * @param borderWidth Width of the border for handling mouse dragging
+ */
+ public ResizeNode(float borderWidth) {
+ this.stroke = new BasicStroke(borderWidth);
+ }
+
+ @PropertySetter("Bounds")
+ @SyncField("bounds")
+ public void setBounds(Rectangle2D bounds) {
+ assert(bounds != null);
+ this.bounds = bounds;
+ }
+
+ @PropertySetter("stroke")
+ @SyncField("stroke")
+ public void setStroke(Stroke stroke) {
+ this.stroke = stroke;
+ }
+
+ /**
+ * Is dragging (resizing) active
+ * @return
+ */
+ public boolean dragging() {
+ return dragging;
+ }
+
+ /**
+ * Set a ResizeListener for this node
+ * @param listener ResizeListener
+ */
+ public void setResizeListener(ResizeListener listener) {
+ this.resizeListener = listener;
+ }
+
+ @Override
+ public void cleanup() {
+ removeEventHandler(this);
+ super.cleanup();
+ }
+
+ @Override
+ public void init() {
+ super.init();
+ addEventHandler(this);
+ }
+
+ /**
+ * Outline stroke shape of the bounds of this node
+ *
+ * @return Outline stroke shape of the bounds of this node
+ */
+ protected Shape getOutline() {
+ return stroke.createStrokedShape(new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()));
+ }
+
+ /**
+ * Dragging is started on mouse pressed, if mouse was pressed on the edge of bounds
+ */
+ @Override
+ protected boolean mouseButtonPressed(MouseButtonPressedEvent event) {
+ if(bounds != null && NodeUtil.isSelected(this, 1)) {
+ // get mouse position
+ Point2D local = controlToLocal( event.controlPosition );
+ local = parentToLocal(local);
+
+ // Get outline of this node
+ Shape outline = getOutline();
+
+ if (outline.contains(local)) {
+ dragging = true;
+ return true;
+ }
+ }
+ return super.mouseButtonPressed(event);
+ }
+
+ /**
+ * Get resize cursor for the location where the drag started.
+ *
+ * @param local Point of origin for drag
+ * @return Cursor int
+ */
+ private int getCursorDirection(Point2D local) {
+ float width = ((BasicStroke)stroke).getLineWidth();
+
+ // Check the direction of the resize
+ int cursor = 0;
+ if (local.getX() >= bounds.getX() - width / 2 &&
+ local.getX() <= bounds.getX() + width / 2)
+ // West side
+ cursor = Cursor.W_RESIZE_CURSOR;
+ else if (local.getX() >= bounds.getMaxX() - width / 2 &&
+ local.getX() <= bounds.getMaxX() + width / 2)
+ // East size
+ cursor = Cursor.E_RESIZE_CURSOR;
+
+ if(local.getY() >= bounds.getY() - width / 2 &&
+ local.getY() <= bounds.getY() + width / 2) {
+ // North side
+ if(cursor == Cursor.W_RESIZE_CURSOR)
+ cursor = Cursor.NW_RESIZE_CURSOR;
+ else if(cursor == Cursor.E_RESIZE_CURSOR)
+ cursor = Cursor.NE_RESIZE_CURSOR;
+ else
+ cursor = Cursor.N_RESIZE_CURSOR;
+ } else if(local.getY() >= bounds.getMaxY() - width / 2 &&
+ local.getY() <= bounds.getMaxY() + width / 2) {
+ // South side
+ if(cursor == Cursor.W_RESIZE_CURSOR)
+ cursor = Cursor.SW_RESIZE_CURSOR;
+ else if(cursor == Cursor.E_RESIZE_CURSOR)
+ cursor = Cursor.SE_RESIZE_CURSOR;
+ else
+ cursor = cursor | Cursor.S_RESIZE_CURSOR;
+ }
+ return cursor;
+ }
+
+ double dragTolerance = 0.5;
+
+ @Override
+ protected boolean mouseMoved(MouseMovedEvent e) {
+ if(dragging) {
+ // If dragging is active and mouse is moved enough, resize the element
+ Point2D local = controlToLocal( e.controlPosition );
+ local = parentToLocal(local);
+
+ Rectangle2D bounds = getBoundsInLocal().getBounds2D();
+
+ if(Math.abs(bounds.getMaxX() - local.getX()) > dragTolerance ||
+ Math.abs(bounds.getMaxY() - local.getY()) > dragTolerance) {
+ resize(local, false);
+ }
+ return true;
+ } else if(NodeUtil.isSelected(this, 1)){
+ // Dragging is not active. Change mouse cursor if entered or exited border
+ Point2D local = controlToLocal( e.controlPosition );
+ local = parentToLocal(local);
+
+ Shape outline = getOutline();
+ if (outline.contains(local)) {
+ cursor = getCursorDirection(local);
+ CanvasContext ctx = (CanvasContext)e.getContext();
+ ctx.getMouseCursorContext().setCursor(e.mouseId, Cursor.getPredefinedCursor(cursor));
+ } else if(cursor != 0) {
+ cursor = 0;
+ CanvasContext ctx = (CanvasContext)e.getContext();
+ ctx.getMouseCursorContext().setCursor(e.mouseId, Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+
+ }
+ return super.mouseMoved(e);
+ }
+
+ @Override
+ protected boolean mouseDragged(MouseDragBegin e) {
+ if(dragging) {
+ return true; // Consume event
+ } else {
+ return false;
+ }
+ }
+
+ private static double minSize = 5; // Minimum size for an element
+
+ private void resize(Point2D local, boolean synchronize) {
+ Rectangle2D bounds = getBoundsInLocal().getBounds2D();
+ double x = bounds.getX();
+ double y = bounds.getY();
+ double w = bounds.getWidth();
+ double h = bounds.getHeight();
+ double dx = 0, dy = 0;
+
+ /*
+ * Resize to east and north:
+ *
+ * Move x and y coordinates and resize to get maxX and maxY to stay in their place
+ */
+ if(cursor == Cursor.W_RESIZE_CURSOR || cursor == Cursor.NW_RESIZE_CURSOR || cursor == Cursor.SW_RESIZE_CURSOR) {
+ double dw = local.getX() - x;
+ if(w - dw < minSize)
+ dw = w - minSize;
+ w = w - dw;
+
+
+ if(TranslateEdge.WEST.equals(xTranslateEdge))
+ dx = dw;
+ }
+
+ if(cursor == Cursor.N_RESIZE_CURSOR || cursor == Cursor.NW_RESIZE_CURSOR || cursor == Cursor.NE_RESIZE_CURSOR ) {
+ double dh = local.getY() - y;
+ if(h - dh < minSize)
+ dh = h - minSize;
+ h = h - dh;
+
+ if(TranslateEdge.NORTH.equals(YTranslateEdge))
+ dy = dh;
+ }
+
+ /*
+ * Resize to west and south:
+ *
+ * Adjust width and height
+ */
+ if(cursor == Cursor.E_RESIZE_CURSOR || cursor == Cursor.NE_RESIZE_CURSOR || cursor == Cursor.SE_RESIZE_CURSOR) {
+ double dw = local.getX() - bounds.getMaxX();
+ w = w + dw > minSize ? w + dw : minSize;
+
+ if(TranslateEdge.EAST.equals(xTranslateEdge))
+ dx = w - bounds.getWidth();
+ }
+
+ if(cursor == Cursor.S_RESIZE_CURSOR || cursor == Cursor.SW_RESIZE_CURSOR || cursor == Cursor.SE_RESIZE_CURSOR) {
+ double dh = local.getY() - bounds.getMaxY();
+ h = h + dh > minSize ? h + dh : minSize;
+
+ if(TranslateEdge.SOUTH.equals(YTranslateEdge))
+ dy = h - bounds.getHeight();
+ }
+
+ /*
+ * Set bounds and transform to the element before calling resize listener.
+ * This prevents unwanted movement due to unsynchronized transform and bounds.
+ */
+ this.bounds.setRect(x, y, w, h);
+
+ AffineTransform at = new AffineTransform();
+ at.translate(dx, dy);
+ if(resizeListener != null)
+ resizeListener.elementResized(this.bounds, at, synchronize);
+ }
+
+ @Override
+ protected boolean mouseButtonReleased(MouseButtonReleasedEvent e) {
+ if(dragging) {
+ // Stop resizing and set the size to its final state.
+ Point2D local = controlToLocal( e.controlPosition );
+ local = parentToLocal(local);
+
+ resize(local, true);
+ dragging = false;
+
+ // Revert cursor to normal.
+ CanvasContext ctx = (CanvasContext)e.getContext();
+ cursor = Cursor.DEFAULT_CURSOR;
+ ctx.getMouseCursorContext().setCursor(e.mouseId, Cursor.getPredefinedCursor(cursor));
+ }
+ return super.mouseButtonReleased(e);
+ }
+
+ @Override
+ public int getEventMask() {
+ return super.getEventMask() | EventTypes.MouseButtonPressedMask
+ | EventTypes.MouseMovedMask
+ | EventTypes.MouseButtonReleasedMask
+ ;
+ }
+
+ @Override
+ public Rectangle2D getBoundsInLocal() {
+ if(bounds == null) return null;
+ return bounds.getBounds2D();
+ }
+
+ @Override
+ public void render(Graphics2D g2d) {
+ // Do not draw anything
+ }
+
+ /**
+ * Set which edge should affect X-translation
+ * @param xTranslateEdge TranslateEdge.NONE, EAST or WEST
+ */
+ public void setxTranslateEdge(TranslateEdge xTranslateEdge) {
+ this.xTranslateEdge = xTranslateEdge;
+ }
+
+ /**
+ * Set which edge should affect Y-translation
+ * @param yTranslateEdge TranslateEdge.NONE, SOUTH or NORTH
+ */
+ public void setYTranslateEdge(TranslateEdge yTranslateEdge) {
+ YTranslateEdge = yTranslateEdge;
+ }
+}