/*
* ******************************************************************************
* * Copyright 2015 See AUTHORS file.
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
* *****************************************************************************
*/
package com.uwsoft.editor.view.stage.tools;
import com.badlogic.ashley.core.Entity;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.math.*;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.viewport.Viewport;
import com.commons.MsgAPI;
import com.uwsoft.editor.Overlap2DFacade;
import com.uwsoft.editor.proxy.CursorManager;
import com.uwsoft.editor.renderer.components.DimensionsComponent;
import com.uwsoft.editor.renderer.components.ParentNodeComponent;
import com.uwsoft.editor.renderer.components.TransformComponent;
import com.uwsoft.editor.renderer.data.LayerItemVO;
import com.uwsoft.editor.renderer.utils.ComponentRetriever;
import com.uwsoft.editor.utils.EntityBounds;
import com.uwsoft.editor.utils.runtime.EntityUtils;
import com.uwsoft.editor.view.stage.Sandbox;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
/**
* Created by azakhary on 4/30/2015.
*/ //TODO all the new Vector2 instances should be replaced by tmp instances
public class SelectionTool extends SimpleTool {
public static final String NAME = "SELECTION_TOOL";
protected Sandbox sandbox;
private boolean isDragging = false;
private boolean currentTouchedItemWasSelected = false;
private boolean isCastingRectangle = false;
private Rectangle tmp = new Rectangle();
private final EntityBounds tmpEntityBounds = new EntityBounds();
private final float[] draggedRectanglePoints = new float[8];
private final Polygon tmpPolygon1 = new Polygon();
private final Polygon tmpPolygon2 = new Polygon();
private Vector2 directionVector = null;
private Vector2 dragMouseStartPosition;
private HashMap<Entity, Vector2> dragStartPositions = new HashMap<>();
private HashMap<Entity, Vector2> dragTouchDiff = new HashMap<>();
private TransformComponent transformComponent;
private DimensionsComponent dimensionsComponent;
public SelectionTool() {
}
@Override
public String getName() {
return NAME;
}
@Override
public void initTool() {
super.initTool();
sandbox = Sandbox.getInstance();
// set cursor
CursorManager cursorManager = Overlap2DFacade.getInstance().retrieveProxy(CursorManager.NAME);
cursorManager.setCursor(CursorManager.NORMAL);
}
@Override
public boolean stageMouseDown(float x, float y) {
sandbox = Sandbox.getInstance();
boolean setOpacity = false;
//TODO: Anyone can explain what was the purpose of this?
if (!Gdx.input.isKeyPressed(Input.Keys.SPACE)) {
setOpacity = true;
}
// transform stage coordinates to screen coordinates
Vector2 screenCoords = Sandbox.getInstance().worldToScreen(x, y);
// preparing selection tool rectangle to follow mouse
sandbox.prepareSelectionRectangle(screenCoords.x, screenCoords.y, setOpacity);
return true;
}
@Override
public void stageMouseUp(float x, float y) {
// selection is complete, this will check for what get caught in selection rect, and select 'em
selectionComplete();
isCastingRectangle = false;
}
@Override
public void stageMouseDragged(float x, float y) {
sandbox = Sandbox.getInstance();
isCastingRectangle = true;
// transform stage coordinates to screen coordinates
Vector2 screenCoords = Sandbox.getInstance().worldToScreen(x, y);
sandbox.selectionRec.setWidth(screenCoords.x - sandbox.selectionRec.getX());
sandbox.selectionRec.setHeight(screenCoords.y - sandbox.selectionRec.getY());
}
@Override
public void stageMouseDoubleClick(float x, float y) {
Entity currentView = sandbox.getCurrentViewingEntity();
ParentNodeComponent parentNodeComponent = ComponentRetriever.get(currentView, ParentNodeComponent.class);
if(parentNodeComponent != null) {
Overlap2DFacade.getInstance().sendNotification(MsgAPI.ACTION_CAMERA_CHANGE_COMPOSITE, parentNodeComponent.parentEntity);
}
}
@Override
public boolean itemMouseDown(Entity entity, float x, float y) {
sandbox = Sandbox.getInstance();
Overlap2DFacade facade = Overlap2DFacade.getInstance();
currentTouchedItemWasSelected = sandbox.getSelector().getCurrentSelection().contains(entity);
if(isEntityVisible(entity)) {
// if shift is pressed we are in add/remove selection mode
if (isShiftPressed()) {
//TODO block selection handling (wat?)
if (!currentTouchedItemWasSelected) {
// item was not selected, adding it to selection
Set<Entity> items = new HashSet<>();
items.add(entity);
facade.sendNotification(MsgAPI.ACTION_ADD_SELECTION, items);
}
} else {
//TODO fix and uncomment layer locking
// if (item.isLockedByLayer()) {
// // this is considered empty space click and thus should release all selections
// facade.sendNotification(MsgAPI.ACTION_SET_SELECTION, null);
// commands.getSelector().clearSelections();
// return false;
// } else {
if (!currentTouchedItemWasSelected) {
// get selection, add this item to selection
Set<Entity> items = new HashSet<>();
items.add(entity);
facade.sendNotification(MsgAPI.ACTION_SET_SELECTION, items);
}
//}
}
}
// remembering local touch position for each of selected boxes, if planning to drag
dragStartPositions.clear();
dragTouchDiff.clear();
for (Entity itemInstance : sandbox.getSelector().getCurrentSelection()) {
transformComponent = ComponentRetriever.get(itemInstance, TransformComponent.class);
dragTouchDiff.put(itemInstance, new Vector2(x - transformComponent.x, y - transformComponent.y));
dragStartPositions.put(itemInstance, new Vector2(transformComponent.x, transformComponent.y));
}
dragMouseStartPosition = new Vector2(x, y);
// pining UI to update current item properties tools
Overlap2DFacade.getInstance().sendNotification(MsgAPI.ITEM_DATA_UPDATED, entity);
return true;
}
@Override
public void itemMouseDragged(Entity entity, float x, float y) {
sandbox = Sandbox.getInstance();
if(isDragging == false && (Gdx.input.isKeyPressed(Input.Keys.ALT_LEFT) || Gdx.input.isKeyPressed(Input.Keys.ALT_RIGHT))) { // first drag iteration and is copy mode
// we need to copy/paste the item in place, the set it as selection and draggable, then perform the drag.
Overlap2DFacade.getInstance().sendNotification(MsgAPI.ACTION_COPY);
Overlap2DFacade.getInstance().sendNotification(MsgAPI.ACTION_PASTE);
dragStartPositions.clear();
dragTouchDiff.clear();
for (Entity itemInstance : sandbox.getSelector().getCurrentSelection()) {
transformComponent = ComponentRetriever.get(itemInstance, TransformComponent.class);
dragTouchDiff.put(itemInstance, new Vector2(x - transformComponent.x, y - transformComponent.y));
dragStartPositions.put(itemInstance, new Vector2(transformComponent.x, transformComponent.y));
}
dragMouseStartPosition = new Vector2(x, y);
// pining UI to update current item properties tools
Overlap2DFacade.getInstance().sendNotification(MsgAPI.ITEM_DATA_UPDATED, entity);
}
isDragging = true;
float gridSize = Sandbox.getInstance().getWorldGridSize();
if (isShiftPressed()) {
// check if we have a direction vector
if(directionVector == null) {
directionVector = new Vector2();
if(Math.abs(x - dragMouseStartPosition.x) >= Math.abs(y - dragMouseStartPosition.y)) {
directionVector.x = 1;
directionVector.y = 0;
} else {
directionVector.x = 0;
directionVector.y = 1;
}
}
} else {
directionVector = null;
}
if (Gdx.input.isButtonPressed(Input.Buttons.LEFT)) {
sandbox.dirty = true;
float newX;
float newY;
newX = MathUtils.floor(x / gridSize) * gridSize;
newY = MathUtils.floor(y / gridSize) * gridSize;
if (isShiftPressed()) {
if(directionVector.x == 0) {
newX = dragMouseStartPosition.x;
}
if(directionVector.y == 0) {
newY = dragMouseStartPosition.y;
}
}
// Selection rectangles should move and follow along
for (Entity itemInstance : sandbox.getSelector().getCurrentSelection()) {
transformComponent = ComponentRetriever.get(itemInstance, TransformComponent.class);
Vector2 diff = new Vector2(dragTouchDiff.get(itemInstance));
diff.x = MathUtils.floor(diff.x / gridSize) * gridSize;
diff.y = MathUtils.floor(diff.y/ gridSize) * gridSize;
transformComponent.x = (newX - diff.x);
transformComponent.y = (newY - diff.y);
//value.hide();
// pining UI to update current item properties tools
Overlap2DFacade.getInstance().sendNotification(MsgAPI.ITEM_DATA_UPDATED, itemInstance);
}
}
}
@Override
public void itemMouseUp(Entity entity, float x, float y) {
sandbox = Sandbox.getInstance();
Overlap2DFacade facade = Overlap2DFacade.getInstance();
if (currentTouchedItemWasSelected && !isDragging) {
// item was selected (and no dragging was performed), so we need to release it
if (isShiftPressed()) {
Set<Entity> items = new HashSet<>();
items.add(entity);
facade.sendNotification(MsgAPI.ACTION_RELEASE_SELECTION, items);
}
}
// re-show all selection rectangles as clicking/dragging is finished
// TODO: this has to be done via notification
//for (SelectionRectangle value : commands.getSelector().getCurrentSelection().values()) {
// value.show();
//}
if (sandbox.dirty) {
sandbox.saveSceneCurrentSceneData();
}
sandbox.dirty = false;
// if we were dragging, need to remember new position
if(isDragging) {
// sets item position, and puts things into undo-redo que
Array<Object[]> payloads = new Array<>();
for (Entity itemInstance : sandbox.getSelector().getCurrentSelection()) {
transformComponent = ComponentRetriever.get(itemInstance, TransformComponent.class);
Vector2 newPosition = new Vector2(transformComponent.x, transformComponent.y);
Vector2 oldPosition = dragStartPositions.get(itemInstance);
Object payload[] = new Object[3];
payload[0] = itemInstance;
payload[1] = newPosition;
payload[2] = oldPosition;
payloads.add(payload);
}
Overlap2DFacade.getInstance().sendNotification(MsgAPI.ACTION_ITEMS_MOVE_TO, payloads);
}
isDragging = false;
}
@Override
public void itemMouseDoubleClick(Entity item, float x, float y) {
if(sandbox.getSelector().selectionIsComposite()) {
Overlap2DFacade.getInstance().sendNotification(MsgAPI.ACTION_CAMERA_CHANGE_COMPOSITE, item);
}
}
private boolean isShiftPressed() {
return Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT)
|| Gdx.input.isKeyPressed(Input.Keys.SHIFT_RIGHT);
}
private void selectionComplete() {
sandbox = Sandbox.getInstance();
Overlap2DFacade facade = Overlap2DFacade.getInstance();
OrthographicCamera camera = Sandbox.getInstance().getCamera();
Viewport viewport = Sandbox.getInstance().getViewport();
HashSet<Entity> freeItems = sandbox.getSelector().getAllFreeItems();
// when touch is up, selection process stops, and if any items got "caught" in they should be selected.
// hiding selection rectangle
sandbox.selectionRec.setOpacity(0.0f);
//ArrayList<Entity> curr = new ArrayList<Entity>();
Set<Entity> curr = new HashSet<>();
Rectangle sR = sandbox.screenToWorld(sandbox.selectionRec.getRect());
draggedRectanglePoints[0] = sR.x;
draggedRectanglePoints[1] = sR.y;
draggedRectanglePoints[2] = sR.x + sR.width;
draggedRectanglePoints[3] = sR.y;
draggedRectanglePoints[4] = sR.x + sR.width;
draggedRectanglePoints[5] = sR.y + sR.height;
draggedRectanglePoints[6] = sR.x;
draggedRectanglePoints[7] = sR.y + sR.height;
for (Entity entity : freeItems) {
transformComponent = ComponentRetriever.get(entity, TransformComponent.class);
dimensionsComponent = ComponentRetriever.get(entity, DimensionsComponent.class);
//if (!freeItems.get(i).isLockedByLayer() && Intersector.overlaps(sR, new Rectangle(entity.getX(), entity.getY(), entity.getWidth(), entity.getHeight()))) {
tmpPolygon1.setVertices(draggedRectanglePoints);
tmpPolygon2.setVertices(tmpEntityBounds.getBoundPoints(entity));
boolean intersects = Intersector.overlapConvexPolygons(tmpPolygon1, tmpPolygon2);
if (isEntityVisible(entity) && intersects) {
curr.add(entity);
}
}
if (curr.size() == 0) {
facade.sendNotification(MsgAPI.EMPTY_SPACE_CLICKED);
//remove visual selection command
facade.sendNotification(MsgAPI.ACTION_SET_SELECTION, null);
return;
}
facade.sendNotification(MsgAPI.ACTION_SET_SELECTION, curr);
}
private boolean isEntityVisible(Entity e) {
LayerItemVO layer = EntityUtils.getEntityLayer(e);
return layer != null && layer.isVisible;
}
@Override
public void keyDown(Entity entity, int keycode) {
boolean isControlPressed = isControlPressed();
// the amount of pixels by which to move item if moving
float deltaMove = 1f/Sandbox.getInstance().getPixelPerWU();
if (Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT)) {
// if shift is pressed, move boxes by 20 pixels instead of one
deltaMove = 20f/Sandbox.getInstance().getPixelPerWU(); //pixels
}
if (sandbox.getGridSize() > 1) {
deltaMove = sandbox.getGridSize();
if (Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT)) {
// if shift is pressed, move boxes 3 times more then the grid size
deltaMove *= 3;
}
}
if(!isControlPressed) {
if (keycode == Input.Keys.UP) {
// moving UP
sandbox.getSelector().moveSelectedItemsBy(0, deltaMove);
}
if (keycode == Input.Keys.DOWN) {
// moving down
sandbox.getSelector().moveSelectedItemsBy(0, -deltaMove);
}
if (keycode == Input.Keys.LEFT) {
// moving left
sandbox.getSelector().moveSelectedItemsBy(-deltaMove, 0);
}
if (keycode == Input.Keys.RIGHT) {
//moving right
sandbox.getSelector().moveSelectedItemsBy(deltaMove, 0);
}
}
// Delete
if (keycode == Input.Keys.DEL || keycode == Input.Keys.FORWARD_DEL) {
Overlap2DFacade.getInstance().sendNotification(MsgAPI.ACTION_DELETE);
}
}
@Override
public void keyUp(Entity entity, int keycode) {
}
private boolean isControlPressed() {
return Gdx.input.isKeyPressed(Input.Keys.SYM)
|| Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT)
|| Gdx.input.isKeyPressed(Input.Keys.CONTROL_RIGHT);
}
}