package com.project.website.canvas.client.worksheet;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
import com.project.shared.client.events.SimpleEvent;
import com.project.shared.client.utils.ElementUtils;
import com.project.shared.data.Point2D;
import com.project.shared.data.Rectangle;
import com.project.shared.utils.PointUtils;
import com.project.shared.utils.PointUtils.ConstraintMode;
import com.project.website.canvas.client.canvastools.base.interfaces.CanvasToolFrame;
import com.project.website.canvas.client.resources.CanvasResources;
import com.project.website.canvas.client.shared.UndoManager;
import com.project.website.canvas.client.shared.UndoManager.UndoRedoPair;
import com.project.website.canvas.client.worksheet.interfaces.ElementDragManager;
import com.project.website.canvas.client.worksheet.interfaces.MouseMoveOperationHandler;
import com.project.website.canvas.client.worksheet.interfaces.ToolFrameTransformer;
public class ToolFrameTransformerImpl implements ToolFrameTransformer
{
// 1 = best, higer value means bigger angle steps (lower resolution)
private static final int ROTATION_ROUND_RESOLUTION = 3;
private static final double GRID_RESOLUTION = 50;
protected static final int DEFAULT_ANIMATION_DURATION = 300;
private final ElementDragManager _elementDragManager;
private double gridResolution = GRID_RESOLUTION;
private boolean snapToGrid = false;
public ToolFrameTransformerImpl(Widget container, Widget dragPanel, SimpleEvent<Void> stopOperationEvent)
{
_elementDragManager = new ElementDragManagerImpl(container, dragPanel,
CanvasResources.INSTANCE.main().drag(), stopOperationEvent);
dragPanel.getElement();
}
@Override
public ElementDragManager getElementDragManager()
{
return _elementDragManager;
}
@Override
public void setToolFramePosition(CanvasToolFrame toolFrame, Point2D pos)
{
this.setToolFramePosition(toolFrame, pos, 0);
}
@Override
public void setToolFramePosition(final CanvasToolFrame toolFrame, Point2D pos, int animationDuration)
{
ElementUtils.setElementCSSPosition(toolFrame.asWidget().getElement(), pos, animationDuration);
toolFrame.onTransformed();
}
@Override
public void startDragCanvasToolFrames(Iterable<CanvasToolFrame> toolFrames)
{
for (CanvasToolFrame toolFrame : toolFrames)
{
startDragCanvasToolFrame(toolFrame);
}
}
@Override
public void startDragCanvasToolFrame(final CanvasToolFrame toolFrame)
{
final Element toolFrameElement = toolFrame.asWidget().getElement();
final Point2D initialPos = ElementUtils.getElementCSSPosition(toolFrameElement);
final Point2D originalOffsetFromFramePos = ElementUtils.getMousePositionRelativeToElement(RootPanel.getBodyElement())
.minus(initialPos);
MouseMoveOperationHandler handler = new MouseMoveOperationHandler() {
@Override public void onStop(Point2D pos) {
toolFrame.setDragging(false);
addDragUndoStep(toolFrame, initialPos, ElementUtils.getElementAbsolutePosition(toolFrameElement));
}
@Override public void onStart() {
toolFrame.setDragging(true);
}
@Override public void onMouseMove(Point2D pos) {
setToolFramePosition(toolFrame, calcDragTargetPos(initialPos, originalOffsetFromFramePos, pos));
}
@Override public void onCancel() {
setToolFramePosition(toolFrame, initialPos);
toolFrame.setDragging(false);
}
};
_elementDragManager.startMouseMoveOperation(toolFrameElement, RootPanel.getBodyElement(),
Point2D.zero, handler, ElementDragManager.StopCondition.STOP_CONDITION_MOVEMENT_STOP);
}
/**
* Starts (and handles) a resize operation on a CanvasToolFrame. Implementation logic:
*
* <el><li>If we change the size of an element when it's rotated around it's center (transform-origin: "") then
* it's position changes with the size (to keep the center in place). We must therefore switch the element's
* transform-origin to the top-left corner during resize, and switch back to default (rotate around center) after.</li>
*
* <li>Switching the transformation origin while the element is rotate causes the position to jump. So we also need to
* move the element before the resize and move it back after the resize, so that it will appear in the same position</li>
*
* </el>
*/
@Override
public void startResizeCanvasToolFrame(final CanvasToolFrame toolFrame)
{
final Element toolFrameElement = toolFrame.asWidget().getElement();
final double angle = Math.toRadians(ElementUtils.getRotation(toolFrameElement));
final Point2D initialSize = toolFrame.getToolSize();
final Point2D startDragPos = ElementUtils.getMousePositionRelativeToElement(RootPanel.getBodyElement());
final Point2D startPos = startDragPos.minus(ElementUtils.getMousePositionRelativeToElement(toolFrameElement));
final Rectangle initialToolFrameRect = ElementUtils.getElementOffsetRectangle(toolFrameElement);
final Point2D initialTopLeft = initialToolFrameRect.getCorners().topLeft;
// Change the rotation axis to the top-left corner, the element will then appear in a different place on the screen
ElementUtils.setTransformOriginTopLeft(toolFrameElement);
// Now when we set the element's position, we are setting the position of the top-left corner (the new transform origin).
// Move the element so that its top-left corner is the same as before switching the rotation axis.
setToolFramePosition(toolFrame, initialTopLeft);
MouseMoveOperationHandler handler = new MouseMoveOperationHandler() {
@Override public void onStop(final Point2D pos) {
UndoManager.get().addAndRedo(toolFrame, new UndoRedoPair() {
@Override public void undo() {
onCancel();
}
@Override public void redo() {
onResizeFrameFinished(toolFrame, toolFrameElement, angle, initialSize, startDragPos, initialTopLeft, pos);
}
});
}
@Override public void onStart() { }
@Override public void onMouseMove(Point2D pos) {
Point2D size = sizeFromRotatedSizeOffset(angle, initialSize, startDragPos, pos);
toolFrame.setToolSize(transformMovement(size, initialSize));
}
@Override public void onCancel() {
toolFrame.setToolSize(initialSize);
ElementUtils.resetTransformOrigin(toolFrameElement);
setToolFramePosition(toolFrame, startPos);
}
};
_elementDragManager.startMouseMoveOperation(toolFrameElement, RootPanel.getBodyElement(),
Point2D.zero, handler, ElementDragManager.StopCondition.STOP_CONDITION_MOVEMENT_STOP);
}
@Override
public void startRotateCanvasToolFrame(final CanvasToolFrame toolFrame)
{
Rectangle initialRect = ElementUtils.getElementAbsoluteRectangle(toolFrame.asWidget().getElement());
final Point2D initialCenter = initialRect.getCenter();
Point2D unrotatedBottomLeftRelativeToCenter = initialRect.getSize().mulCoords(-0.5, 0.5);
final double unrotatedBottomLeftAngle = Math.toDegrees(unrotatedBottomLeftRelativeToCenter.getRadians());
final double startAngle = ElementUtils.getRotation(toolFrame.asWidget().getElement());
MouseMoveOperationHandler handler = new MouseMoveOperationHandler() {
@Override public void onStop(final Point2D pos) {
UndoManager.get().add(toolFrame, new UndoRedoPair() {
@Override public void undo() {
ElementUtils.setRotation(toolFrame.asWidget().getElement(), startAngle, DEFAULT_ANIMATION_DURATION);
toolFrame.onTransformed();
}
@Override public void redo() {
rotateToolFrame(toolFrame, initialCenter, unrotatedBottomLeftAngle, pos, DEFAULT_ANIMATION_DURATION);
}
});
}
@Override public void onStart() { }
@Override public void onMouseMove(Point2D pos) {
rotateToolFrame(toolFrame, initialCenter, unrotatedBottomLeftAngle, pos, 0);
}
@Override public void onCancel() {
ElementUtils.setRotation(toolFrame.asWidget().getElement(), startAngle);
toolFrame.onTransformed();
}
};
_elementDragManager.startMouseMoveOperation(toolFrame.asWidget().getElement(), RootPanel.getBodyElement(),
Point2D.zero, handler, ElementDragManager.StopCondition.STOP_CONDITION_MOVEMENT_STOP);
}
private Point2D sizeFromRotatedSizeOffset(final double angle, final Point2D initialSize,
final Point2D startDragPos, Point2D pos)
{
Point2D rotatedSizeOffset = pos.minus(startDragPos);
Point2D sizeOffset = rotatedSizeOffset.getRotated(-angle);
Point2D size = Point2D.max(initialSize.plus(sizeOffset), Point2D.zero);
return size;
}
private int roundedAngle(double rotation)
{
return (int) Math.round(ROTATION_ROUND_RESOLUTION * (rotation / ROTATION_ROUND_RESOLUTION));
}
private Point2D transformMovement(Point2D coords, Point2D initialCoords)
{
return transformMovement(coords, initialCoords, true);
}
private Point2D transformMovement(Point2D coords, Point2D initialCoords, boolean allowMean)
{
Event event = Event.getCurrentEvent();
if (null == event) {
return this.applySnapToGrid(coords);
}
Point2D sizeDelta = coords.minus(initialCoords);
ConstraintMode mode = ConstraintMode.NONE;
if (allowMean && event.getCtrlKey()) {
mode = ConstraintMode.KEEP_RATIO;
}
else if (event.getShiftKey() && event.getAltKey()) {
// do nothing here.
}
else if (event.getShiftKey()) {
mode = ConstraintMode.SNAP_Y;
}
else if (event.getAltKey()) {
mode = ConstraintMode.SNAP_X;
}
sizeDelta = PointUtils.constrain(sizeDelta, initialCoords, mode);
if (this.snapToGrid || (event.getShiftKey() && event.getAltKey())) {
sizeDelta = applySnapToGrid(sizeDelta);
}
return this.applySnapToGrid(initialCoords.plus(sizeDelta));
}
@Override
public Point2D applySnapToGrid(Point2D sizeDelta)
{
if (this.snapToGrid) {
return sizeDelta.mul(1/gridResolution).mul(gridResolution);
}
return sizeDelta;
}
private Point2D calcDragTargetPos(final Point2D initialPos, final Point2D originalOffsetFromFramePos, Point2D pos)
{
return transformMovement(pos.minus(originalOffsetFromFramePos), initialPos, false);
}
private void addDragUndoStep(final CanvasToolFrame toolFrame, final Point2D initialPos, final Point2D targetPos)
{
UndoManager.get().add(toolFrame, new UndoRedoPair() {
@Override public void undo() {
//Point2D currentPos = ElementUtils.getElementAbsolutePosition(toolFrame.asWidget().getElement());
//.minus(targetPos).plus(currentPos)
setToolFramePosition(toolFrame, initialPos, DEFAULT_ANIMATION_DURATION);
}
@Override public void redo() {
setToolFramePosition(toolFrame, targetPos, DEFAULT_ANIMATION_DURATION);
}
});
}
private void onResizeFrameFinished(final CanvasToolFrame toolFrame, final Element toolFrameElement,
final double angle, final Point2D initialSize, final Point2D startDragPos, final Point2D initialTopLeft,
Point2D pos)
{
Point2D size = sizeFromRotatedSizeOffset(angle, initialSize, startDragPos, pos);
toolFrame.setToolSize(transformMovement(size, initialSize));
// Move the rotation axis back to the center of the element (reset the transform origin)
// The element will jump
ElementUtils.resetTransformOrigin(toolFrameElement);
// Move the element so that its top-left is in the same position as it was before the resize.
Rectangle postResizeToolFrameRect = ElementUtils.getElementOffsetRectangle(toolFrameElement);
Point2D postResizeTopLeft = postResizeToolFrameRect.getCorners().topLeft;
setToolFramePosition(toolFrame, ElementUtils.getElementOffsetPosition(toolFrameElement).minus(postResizeTopLeft).plus(initialTopLeft));
}
private void rotateToolFrame(final CanvasToolFrame toolFrame, final Point2D initialCenter,
final double unrotatedBottomLeftAngle, Point2D pos, int animationDuration)
{
Point2D posRelativeToCenter = pos.minus(initialCenter);
double rotation = Math.toDegrees(posRelativeToCenter.getRadians()) - unrotatedBottomLeftAngle;
ElementUtils.setRotation(toolFrame.asWidget().getElement(), roundedAngle(rotation), animationDuration);
toolFrame.onTransformed();
}
@Override
public double getGridResolution()
{
return gridResolution;
}
@Override
public void setGridResolution(double gridResolution)
{
this.gridResolution = gridResolution;
}
@Override
public boolean isSnapToGrid()
{
return snapToGrid;
}
@Override
public void setSnapToGrid(boolean snapToGrid)
{
this.snapToGrid = snapToGrid;
}
}