/*
* ******************************************************************************
* * 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;
import com.badlogic.ashley.core.Engine;
import com.badlogic.ashley.core.Entity;
import com.badlogic.ashley.core.Family;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Align;
import com.badlogic.gdx.utils.SnapshotArray;
import com.commons.MsgAPI;
import com.commons.view.tools.Tool;
import com.puremvc.patterns.mediator.SimpleMediator;
import com.puremvc.patterns.observer.Notification;
import com.uwsoft.editor.Overlap2DFacade;
import com.uwsoft.editor.controller.commands.AddComponentToItemCommand;
import com.uwsoft.editor.controller.commands.CompositeCameraChangeCommand;
import com.uwsoft.editor.controller.commands.RemoveComponentFromItemCommand;
import com.uwsoft.editor.proxy.CommandManager;
import com.uwsoft.editor.renderer.components.NodeComponent;
import com.uwsoft.editor.renderer.components.ViewPortComponent;
import com.uwsoft.editor.renderer.utils.ComponentRetriever;
import com.uwsoft.editor.view.stage.input.EntityClickListener;
import com.uwsoft.editor.view.stage.input.InputListenerComponent;
import com.uwsoft.editor.view.stage.tools.*;
import com.uwsoft.editor.view.ui.box.UIToolBoxMediator;
import java.awt.*;
import java.util.HashMap;
import java.util.Map;
import static com.uwsoft.editor.view.ui.box.UIToolBox.TOOL_CLICKED;
/**
* Created by sargis on 4/20/15.
*/
public class SandboxMediator extends SimpleMediator<Sandbox> {
private static final String TAG = SandboxMediator.class.getCanonicalName();
public static final String NAME = TAG;
private static final String PREFIX = "com.uwsoft.editor.view.stage.SandboxStageMediator";
public static final String SANDBOX_TOOL_CHANGED = PREFIX + ".SANDBOX_TOOL_CHANGED";
private final Vector2 reducedMoveDirection = new Vector2(0, 0);
private SandboxStageEventListener stageListener;
private Tool hotSwapMemory;
private HashMap<String, Tool> sandboxTools = new HashMap<>();
private Tool currentSelectedTool;
public SandboxMediator() {
super(NAME, Sandbox.getInstance());
}
@Override
public void onRegister() {
super.onRegister();
facade = Overlap2DFacade.getInstance();
stageListener = new SandboxStageEventListener();
getViewComponent().addListener(stageListener);
initTools();
}
private void initTools() {
sandboxTools.put(SelectionTool.NAME, new SelectionTool());
sandboxTools.put(TransformTool.NAME, new TransformTool());
sandboxTools.put(TextTool.NAME, new TextTool());
sandboxTools.put(PointLightTool.NAME, new PointLightTool());
sandboxTools.put(ConeLightTool.NAME, new ConeLightTool());
sandboxTools.put(PanTool.NAME, new PanTool());
sandboxTools.put(PolygonTool.NAME, new PolygonTool());
}
private void setCurrentTool(String toolName) {
currentSelectedTool = sandboxTools.get(toolName);
if (currentSelectedTool != null) {
facade.sendNotification(SANDBOX_TOOL_CHANGED, currentSelectedTool);
currentSelectedTool.initTool();
}
}
@Override
public String[] listNotificationInterests() {
return new String[]{
MsgAPI.SCENE_LOADED,
MsgAPI.TOOL_SELECTED,
MsgAPI.NEW_ITEM_ADDED,
MsgAPI.NEW_TOOL_ADDED,
CompositeCameraChangeCommand.DONE,
AddComponentToItemCommand.DONE,
RemoveComponentFromItemCommand.DONE,
MsgAPI.ITEM_SELECTION_CHANGED
};
}
@Override
public void handleNotification(Notification notification) {
super.handleNotification(notification);
switch (notification.getName()) {
case MsgAPI.SCENE_LOADED:
handleSceneLoaded(notification);
break;
case MsgAPI.TOOL_SELECTED:
setCurrentTool(notification.getBody());
break;
case MsgAPI.NEW_ITEM_ADDED:
addListenerToItem(notification.getBody());
break;
case MsgAPI.NEW_TOOL_ADDED:
addSandboxTool(notification.getBody());
break;
case CompositeCameraChangeCommand.DONE:
initItemListeners();
break;
default:
break;
}
if(currentSelectedTool != null) {
currentSelectedTool.handleNotification(notification);
}
}
private void addSandboxTool(Map.Entry<String, Tool> newTool) {
sandboxTools.put(newTool.getKey(), newTool.getValue());
}
private void handleSceneLoaded(Notification notification) {
//TODO fix and uncomment
//viewComponent.addListener(stageListener);
initItemListeners();
setCurrentTool(SelectionTool.NAME);
Sandbox.getInstance().getCamera().position.set(new Vector2(0, 0), 0);
Overlap2DFacade.getInstance().sendNotification(PanTool.SCENE_PANNED);
}
private void initItemListeners() {
Engine engine = getViewComponent().getEngine();
Family rootFamily = Family.all(ViewPortComponent.class).get();
Entity rootEntity = engine.getEntitiesFor(rootFamily).iterator().next();
NodeComponent nodeComponent = ComponentRetriever.get(rootEntity, NodeComponent.class);
SnapshotArray<Entity> childrenEntities = nodeComponent.children;
for (Entity child: childrenEntities) {
addListenerToItem(child);
}
}
/**
* TODO: this can be changed, as in ideal world entity factory should be adding listener component to ALL entities,
* problem is currently this component is not part of runtime. but it will be.
*
* @param entity
*/
private void addListenerToItem(Entity entity) {
InputListenerComponent inputListenerComponent = entity.getComponent(InputListenerComponent.class);
if(inputListenerComponent == null){
inputListenerComponent = new InputListenerComponent();
entity.add(inputListenerComponent);
}
inputListenerComponent.removeAllListener();
inputListenerComponent.addListener(new SandboxItemEventListener(entity));
}
public Vector2 getStageCoordinates() {
// TODO: remove this shit
Engine engine = getViewComponent().getEngine();
Family rootFamily = Family.all(ViewPortComponent.class).get();
Entity rootEntity = engine.getEntitiesFor(rootFamily).iterator().next();
ViewPortComponent viewPortComponent = ComponentRetriever.get(rootEntity, ViewPortComponent.class);
Vector2 vec = new Vector2(Gdx.input.getX(), Gdx.input.getY());
viewPortComponent.viewPort.unproject(vec);
return vec;
}
public class SandboxItemEventListener extends EntityClickListener {
public SandboxItemEventListener(final Entity entity) {
}
@Override
public boolean touchDown(Entity entity, float x, float y, int pointer, int button) {
super.touchDown(entity, x, y, pointer, button);
switch (button) {
case Input.Buttons.MIDDLE:
// if middle button is pressed - PAN the scene
toolHotSwap(sandboxTools.get(PanTool.NAME));
break;
}
Vector2 coords = getStageCoordinates();
return currentSelectedTool != null && currentSelectedTool.itemMouseDown(entity, coords.x, coords.y);
}
@Override
public void touchUp(Entity entity, float x, float y, int pointer, int button) {
super.touchUp(entity, x, y, pointer, button);
Vector2 coords = getStageCoordinates();
if (button == Input.Buttons.MIDDLE) {
toolHotSwapBack();
}
if (currentSelectedTool != null) {
currentSelectedTool.itemMouseUp(entity, x, y);
if (getTapCount() == 2) {
// this is double click
currentSelectedTool.itemMouseDoubleClick(entity, coords.x, coords.y);
}
}
if (button == Input.Buttons.RIGHT) {
// if right clicked on an item, drop down for current selection
Overlap2DFacade.getInstance().sendNotification(MsgAPI.ITEM_RIGHT_CLICK);
}
}
@Override
public void touchDragged(Entity entity, float x, float y, int pointer) {
Vector2 coords = getStageCoordinates();
if (currentSelectedTool != null) {
currentSelectedTool.itemMouseDragged(entity, coords.x, coords.y);
}
}
}
private class SandboxStageEventListener extends EntityClickListener {
public SandboxStageEventListener() {
setTapCountInterval(.5f);
}
@Override
public boolean keyDown(Entity entity, int keycode) {
boolean isControlPressed = isControlPressed();
Sandbox sandbox = Sandbox.getInstance();
// if control is pressed then z index is getting modified
// TODO: key pressed 0 for unckown, should be removed?
// TODO: need to make sure OSX Command button works too.
if(currentSelectedTool != null) {
currentSelectedTool.keyDown(entity, keycode);
}
// Control pressed as well
if (isControlPressed()) {
if (keycode == Input.Keys.UP) {
// going to front of next item in z-index ladder
sandbox.itemControl.itemZIndexChange(sandbox.getSelector().getCurrentSelection(), true);
}
if (keycode == Input.Keys.DOWN) {
// going behind the next item in z-index ladder
sandbox.itemControl.itemZIndexChange(sandbox.getSelector().getCurrentSelection(), false);
}
if (keycode == Input.Keys.A) {
// Ctrl+A means select all
facade.sendNotification(MsgAPI.ACTION_SET_SELECTION, sandbox.getSelector().getAllFreeItems());
}
// Aligning Selections
if (keycode == Input.Keys.NUM_1 && Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT)) {
sandbox.getSelector().alignSelections(Align.top);
}
if (keycode == Input.Keys.NUM_2 && Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT)) {
sandbox.getSelector().alignSelections(Align.left);
}
if (keycode == Input.Keys.NUM_3 && Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT)) {
sandbox.getSelector().alignSelections(Align.bottom);
}
if (keycode == Input.Keys.NUM_4 && Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT)) {
sandbox.getSelector().alignSelections(Align.right);
}
if (keycode == Input.Keys.NUM_0 || keycode == Input.Keys.NUMPAD_0) {
sandbox.setZoomPercent(100);
sandbox.getCamera().position.set(0 ,0, 0);
facade.sendNotification(MsgAPI.ZOOM_CHANGED);
}
if (keycode == Input.Keys.X) {
facade.sendNotification(MsgAPI.ACTION_CUT);
}
if (keycode == Input.Keys.C) {
facade.sendNotification(MsgAPI.ACTION_COPY);
}
if (keycode == Input.Keys.V) {
facade.sendNotification(MsgAPI.ACTION_PASTE);
}
if (keycode == Input.Keys.T) {
facade.sendNotification(TOOL_CLICKED, TransformTool.NAME);
UIToolBoxMediator toolBoxMediator = facade.retrieveMediator(UIToolBoxMediator.NAME);
toolBoxMediator.setCurrentTool(TransformTool.NAME);
}
if(keycode == Input.Keys.Z) {
if(Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT)) {
CommandManager commandManager = facade.retrieveProxy(CommandManager.NAME);
commandManager.redoCommand();
} else {
CommandManager commandManager = facade.retrieveProxy(CommandManager.NAME);
commandManager.undoCommand();
}
}
}
if (keycode == Input.Keys.V) {
facade.sendNotification(TOOL_CLICKED, SelectionTool.NAME);
UIToolBoxMediator toolBoxMediator = facade.retrieveMediator(UIToolBoxMediator.NAME);
toolBoxMediator.setCurrentTool(SelectionTool.NAME);
}
if (Gdx.input.isKeyPressed(Input.Keys.S) && !isControlPressed()) {
setCurrentTool(SelectionTool.NAME);
UIToolBoxMediator toolBoxMediator = facade.retrieveMediator(UIToolBoxMediator.NAME);
toolBoxMediator.setCurrentTool(SelectionTool.NAME);
}
// if space is pressed, that means we are going to pan, so set cursor accordingly
// TODO: this pan is kinda different from what happens when you press middle button, so things need to merge right
if (keycode == Input.Keys.SPACE) {
sandbox.setCursor(Cursor.HAND_CURSOR);
toolHotSwap(sandboxTools.get(PanTool.NAME));
}
// Zoom
if (keycode == Input.Keys.MINUS && isControlPressed) {
sandbox.zoomDevideBy(2f);
}
if (keycode == Input.Keys.EQUALS && isControlPressed) {
sandbox.zoomDevideBy(0.5f);
}
return true;
}
@Override
public boolean keyUp(Entity entity, int keycode) {
Sandbox sandbox = Sandbox.getInstance();
if (keycode == Input.Keys.DEL) {
// delete selected item
sandbox.getSelector().removeCurrentSelectedItems();
}
if (keycode == Input.Keys.SPACE) {
// if pan mode is disabled set cursor back
sandbox.setCursor(Cursor.DEFAULT_CURSOR);
toolHotSwapBack();
}
if(currentSelectedTool != null) {
currentSelectedTool.keyUp(entity, keycode);
}
return true;
}
@Override
public boolean touchDown(Entity entity, float x, float y, int pointer, int button) {
super.touchDown(entity, x, y, pointer, button);
Sandbox sandbox = Sandbox.getInstance();
// setting key and scroll focus on main area
sandbox.getUIStage().setKeyboardFocus();
sandbox.getUIStage().setScrollFocus(sandbox.getUIStage().midUI);
sandbox.setKeyboardFocus();
// if there was a drop down remove it
// TODO: this is job for front UI to figure out
//commands.getUIStage().mainDropDown.hide();
switch (button) {
case Input.Buttons.MIDDLE:
// if middle button is pressed - PAN the scene
toolHotSwap(sandboxTools.get(PanTool.NAME));
break;
}
if (currentSelectedTool != null) {
currentSelectedTool.stageMouseDown(x, y);
}
return true;
}
@Override
public void touchUp(Entity entity, float x, float y, int pointer, int button) {
super.touchUp(entity, x, y, pointer, button);
if(currentSelectedTool != null) {
currentSelectedTool.stageMouseUp(x, y);
}
Sandbox sandbox = Sandbox.getInstance();
if (button == Input.Buttons.RIGHT) {
// if clicked on empty space, selections need to be cleared
sandbox.getSelector().clearSelections();
// show default dropdown
facade.sendNotification(MsgAPI.SCENE_RIGHT_CLICK, new Vector2(x, y));
return;
}
if (button == Input.Buttons.MIDDLE) {
toolHotSwapBack();
}
if (getTapCount() == 2 && button == Input.Buttons.LEFT) {
doubleClick(entity, x, y);
}
}
private void doubleClick(Entity entity, float x, float y) {
if (currentSelectedTool != null) {
Sandbox sandbox = Sandbox.getInstance();
currentSelectedTool.stageMouseDoubleClick(x, y);
}
}
@Override
public void touchDragged(Entity entity, float x, float y, int pointer) {
if (currentSelectedTool != null) {
Sandbox sandbox = Sandbox.getInstance();
currentSelectedTool.stageMouseDragged(x, y);
}
}
@Override
public boolean scrolled(Entity entity, int amount) {
Sandbox sandbox = Sandbox.getInstance();
// well, duh
if (amount == 0) return false;
// Control pressed as well
if (isControlPressed()) {
float zoomPercent = sandbox.getZoomPercent();
zoomPercent-=amount*4f;
if(zoomPercent < 5 ) zoomPercent = 5;
sandbox.setZoomPercent(zoomPercent);
facade.sendNotification(MsgAPI.ZOOM_CHANGED);
}
// if item is currently being held with mouse (touched in but not touched out)
// mouse scroll should rotate the selection around it's origin
/*
if (commands.isItemTouched) {
for (SelectionRectangle value : commands.getSelector().getCurrentSelection().values()) {
float degreeAmount = 1;
if (amount < 0) degreeAmount = -1;
// And if shift is pressed, the rotation amount is bigger
if (Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT)) {
degreeAmount = degreeAmount * 30;
}
value.getHostAsActor().rotateBy(degreeAmount);
value.update();
}
facade.sendNotification(MsgAPI.ITEM_DATA_UPDATED);
commands.dirty = true;
} else if (Gdx.input.isKeyPressed(Input.Keys.ALT_LEFT)) {
// if not item is touched then we can use this for zoom
commands.zoomBy(amount);
}
*/
return false;
}
private boolean isControlPressed() {
return Gdx.input.isKeyPressed(Input.Keys.SYM)
|| Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT)
|| Gdx.input.isKeyPressed(Input.Keys.CONTROL_RIGHT);
}
private boolean isShiftKey(int keycode) {
return keycode == Input.Keys.SHIFT_LEFT
|| keycode == Input.Keys.SHIFT_RIGHT;
}
private boolean isShiftPressed() {
return Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT)
|| Gdx.input.isKeyPressed(Input.Keys.SHIFT_RIGHT);
}
}
public void toolHotSwap(Tool tool) {
hotSwapMemory = currentSelectedTool;
currentSelectedTool = tool;
}
public void toolHotSwapBack() {
currentSelectedTool = hotSwapMemory;
hotSwapMemory = null;
}
public String getCurrentSelectedToolName() {
return currentSelectedTool != null ? currentSelectedTool.getName() : "";
}
}