/******************************************************************************* * DialogueEditor * Copyright (C) 2013-2014 Pawel Pastuszak * <p> * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * <p> * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * <p> * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package pl.kotcrab.jdialogue.editor; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input.Buttons; import com.badlogic.gdx.Input.Keys; import com.badlogic.gdx.InputMultiplexer; import com.badlogic.gdx.InputProcessor; import com.badlogic.gdx.Preferences; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL10; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType; import com.badlogic.gdx.input.GestureDetector; import com.badlogic.gdx.input.GestureDetector.GestureListener; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; import com.thoughtworks.xstream.XStream; import pl.kotcrab.jdialogue.editor.components.ConnectionRenderer; import pl.kotcrab.jdialogue.editor.components.Connector; import pl.kotcrab.jdialogue.editor.components.DComponent; import pl.kotcrab.jdialogue.editor.components.DComponentType; import pl.kotcrab.jdialogue.editor.components.types.CallbackCheckComponent; import pl.kotcrab.jdialogue.editor.components.types.CallbackComponent; import pl.kotcrab.jdialogue.editor.components.types.ChoiceComponent; import pl.kotcrab.jdialogue.editor.components.types.EndComponent; import pl.kotcrab.jdialogue.editor.components.types.RandomComponent; import pl.kotcrab.jdialogue.editor.components.types.RelayComponent; import pl.kotcrab.jdialogue.editor.components.types.StartComponent; import pl.kotcrab.jdialogue.editor.components.types.TextComponent; import pl.kotcrab.jdialogue.editor.project.Project; import javax.swing.JOptionPane; import java.util.ArrayList; import java.util.Iterator; //TODO przesuwanie kamery, gdy przy rysowaniu zblizymy kursor do kraw�dzie ekranu, u�yteczna funkcja public class Renderer implements ApplicationListener, InputProcessor, GestureListener { private EditorListener listener; private Preferences prefs; private OrthographicCamera camera; private ShapeRenderer shapeRenderer; private SpriteBatch batch; private Rectangle cameraRect; private ArrayList<DComponent> componentList = new ArrayList<DComponent>(); private ArrayList<ArrayList<DComponent>> undoList = new ArrayList<ArrayList<DComponent>>(); private ArrayList<ArrayList<DComponent>> redoList = new ArrayList<ArrayList<DComponent>>(); private ArrayList<DComponent> clipboardList; private Connector selectedConnector = null; private ConnectionRenderer connectionRenderer; private DComponent selectedComponent = null; private ArrayList<DComponent> selectedComponentsList = new ArrayList<DComponent>(); private int attachPointX; private int attachPointY; private RectangularSelection rectangularSelection; private boolean disposed = false; private boolean renderDebug = false; private Matrix4 renderDebugMatrix = new Matrix4(); private Matrix4 renderInfoTextMatrix = new Matrix4(); private KotcrabText infoText; private Project project; private XStream xstream; private boolean dirty; private int panCounter = 0; private boolean cameraDragged; public Renderer (EditorListener listener, XStream xstream) { this.listener = listener; this.xstream = xstream; } @Override public void create () { Assets.load(); prefs = Gdx.app.getPreferences("pl.kotcrab.dialoguelib.editorprefs"); App.setPrefs(prefs); App.loadPrefs(); camera = new OrthographicCamera(1280, 720); camera.position.x = 1280 / 2; camera.position.y = 720 / 2; Touch.setCamera(camera); cameraRect = CameraUtils.calcCameraBoundingRectangle(camera); shapeRenderer = new ShapeRenderer(); batch = new SpriteBatch(); rectangularSelection = new RectangularSelection(new RectangularSelectionListener() { @Override public void finishedDrawing (ArrayList<DComponent> matchingComponents) { selectedComponentsList = matchingComponents; } }, componentList); InputMultiplexer mul = new InputMultiplexer(); mul.addProcessor(this); mul.addProcessor(new GestureDetector(this)); mul.addProcessor(rectangularSelection); Gdx.input.setInputProcessor(mul); connectionRenderer = new ConnectionRenderer(); infoText = new KotcrabText(Assets.consolasFont, "Load or create new project to begin!", false, 0, 0); infoText.setScale(1.4f); } public void resetCamera () { // camera.zoom = 1; camera.setToOrtho(false, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); camera.position.x = Gdx.graphics.getWidth() / 2; camera.position.y = Gdx.graphics.getHeight() / 2; camera.zoom = 1; } public void update () { camera.update(); cameraRect = CameraUtils.calcCameraBoundingRectangle(camera); shapeRenderer.setProjectionMatrix(camera.combined); batch.setProjectionMatrix(camera.combined); for (DComponent comp : componentList) { comp.calcIfVisible(cameraRect); } // this is here for simplicity, because we are using a key and a mouse button // keyDown, will only provide us with key pressed event, and we will have to create flags for keyPressed and ButtonPressed if (Gdx.input.isKeyPressed(Keys.CONTROL_LEFT) && selectedConnector != null && Gdx.input.isButtonPressed(Buttons.LEFT)) { selectedConnector.detach(); } } @Override public void render () { if (!disposed) { update(); int renderedComponents = 0; int renderedConnections = 0; Gdx.gl.glClearColor(0.69f, 0.69f, 0.69f, 1); Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); // why more loops == better // we can draw more shapes/sprites in single batch, wich reduces lwgjl nSwapBuffers and imroves perofmence shapeRenderer.begin(ShapeType.Filled); for (DComponent comp : componentList) { if (comp.isVisible()) { comp.renderShapes(shapeRenderer); renderedComponents++; } } shapeRenderer.end(); shapeRenderer.begin(ShapeType.Line); for (DComponent comp : componentList) { if (comp.isVisible()) comp.renderSelectionOutline(shapeRenderer, Color.BLACK); } shapeRenderer.end(); batch.setShader(Assets.fontDistanceFieldShader); batch.begin(); for (DComponent comp : componentList) { comp.render(batch); } batch.end(); batch.setShader(null); shapeRenderer.begin(ShapeType.Line); if (selectedComponent != null) selectedComponent.renderSelectionOutline(shapeRenderer, Color.ORANGE); // 1 shapeRenderer.end(); // these if's are difrrent, easy to omit... (1,2) if (selectedConnector != null) // 2 { if (Gdx.input.isKeyPressed(Keys.CONTROL_LEFT)) selectedConnector.renderAsSelected(shapeRenderer, Color.RED); else selectedConnector.renderAsSelected(shapeRenderer, Color.ORANGE); if (Gdx.input.isButtonPressed(Buttons.LEFT)) { connectionRenderer.render(shapeRenderer, selectedConnector, Touch.getX(), Touch.getY()); } } rectangularSelection.render(shapeRenderer); shapeRenderer.begin(ShapeType.Line); for (DComponent comp : selectedComponentsList) { comp.renderSelectionOutline(shapeRenderer, Color.RED); } shapeRenderer.end(); connectionRenderer.setCameraCalc(cameraRect); shapeRenderer.setColor(Color.BLACK); shapeRenderer.begin(ShapeType.Line); for (DComponent comp : componentList) { renderedConnections += connectionRenderer.renderLines(shapeRenderer, comp, Gdx.input.isKeyPressed(Keys.CONTROL_LEFT)); // we are counitng how many connections we had drawn } shapeRenderer.end(); shapeRenderer.begin(ShapeType.Filled); for (DComponent comp : componentList) { connectionRenderer.renderTraingles(shapeRenderer, comp, Gdx.input.isKeyPressed(Keys.CONTROL_LEFT)); } shapeRenderer.end(); if (renderDebug) { batch.setProjectionMatrix(renderDebugMatrix); batch.setShader(Assets.fontDistanceFieldShader); batch.begin(); Assets.consolasFont.draw(batch, "FPS: " + Gdx.graphics.getFramesPerSecond(), 10, Gdx.graphics.getHeight() * 10 / 7); Assets.consolasFont.draw(batch, "Components: " + componentList.size(), 10, Gdx.graphics.getHeight() * 10 / 7 - (23 * 1)); Assets.consolasFont.draw(batch, "Rendered components: " + renderedComponents, 10, Gdx.graphics.getHeight() * 10 / 7 - (23 * 2)); Assets.consolasFont.draw(batch, "Rendered connections: " + renderedConnections, 10, Gdx.graphics.getHeight() * 10 / 7 - (23 * 3)); batch.end(); batch.setShader(null); } if (componentList.size() == 0) { batch.setProjectionMatrix(renderInfoTextMatrix); batch.setShader(Assets.fontDistanceFieldShader); batch.begin(); infoText.draw(batch); batch.end(); batch.setShader(null); } } } @Override public void dispose () { if (!disposed) { Assets.dispose(); shapeRenderer.dispose(); batch.dispose(); disposed = true; } } public OrthographicCamera getCamera () { return camera; } @Override public void resize (int width, int height) { Vector3 pos = camera.position.cpy(); camera.setToOrtho(false, width, height); camera.position.x = pos.x; camera.position.y = pos.y; renderDebugMatrix.setToOrtho2D(0, 0, width, height); renderDebugMatrix.scl(0.7f); renderInfoTextMatrix.setToOrtho2D(0, 0, width, height); infoText.center(Gdx.graphics.getWidth()); infoText.setY(Gdx.graphics.getHeight() / 2); } public void addComponent (DComponentType componentType) { dirty = true; if (componentList.size() > 0) // if list is empty, we don't have any sequnce loaded(sequnce must have start component) { switch (componentType) { case CHOICE: componentList.add(new ChoiceComponent(Touch.getX(), Touch.getY())); break; case RANDOM: componentList.add(new RandomComponent(Touch.getX(), Touch.getY())); break; case TEXT: componentList.add(new TextComponent(Touch.getX(), Touch.getY())); break; case CALLBACK: componentList.add(new CallbackComponent(Touch.getX(), Touch.getY())); break; case CBCHECK: componentList.add(new CallbackCheckComponent(Touch.getX(), Touch.getY())); break; case END: componentList.add(new EndComponent(Touch.getX(), Touch.getY())); break; case RELAY: componentList.add(new RelayComponent(Touch.getX(), Touch.getY())); break; default: break; } } else listener.showMsg("Create or load project to edit dialouge structure", "Error", JOptionPane.ERROR_MESSAGE); } /** * Find selected connection * @param x in screen cords * @param y in screen cords */ private void findConnection (float x, float y, boolean touchUp) { x = Touch.calcX(x); y = Touch.calcY(y); boolean found = false; for (DComponent comp : componentList) { if (comp.isVisible() == false) continue; Connector connector = comp.connectionContains(x, y); if (connector != null) // in drawing mode { if (selectedConnector != null && selectedConnector != connector && selectedConnector.getParrentComponent() != connector.getParrentComponent() && touchUp) { if (selectedConnector.isInput() != connector.isInput()) // to prevent connecting 2 outputs or 2 inputs { if (selectedConnector.isInput() && connector.getTarget() == null) { // proper target found, adding connector.addTarget(selectedConnector); selectedConnector.addTarget(connector); return; } if (selectedConnector.isInput() == false && selectedConnector.getTarget() == null) { connector.addTarget(selectedConnector); selectedConnector.addTarget(connector); return; } } } selectedConnector = connector; found = true; break; } } if (found == false) selectedConnector = null; } //@formatter:off @Override public void resume(){} @Override public void pause(){} //@formatter:on public void setRenderCurves (boolean renderCurves) { connectionRenderer.setRenderCurves(renderCurves); } public void setRenderDebug (boolean renderDebug) { this.renderDebug = renderDebug; } public ArrayList<DComponent> getComponentList () { return componentList; } /** * Change componentList and perfom cleanup */ public void setComponentList (ArrayList<DComponent> componentList) { if (dirty) { listener.showSaveDialog(); } this.componentList = componentList; rectangularSelection.setComponentList(componentList); // rectangularselection potrzebuje listy do znajdywania kompponent�w selectedComponent = null; selectedConnector = null; undoList.clear(); redoList.clear(); } // public void setProject(Project project) // { // this.project = project; // } public void undo () { if (undoList.size() > 0) { ArrayList<DComponent> undoComponents = undoList.get(undoList.size() - 1); redoList.add(undoComponents); componentList.addAll(undoComponents); undoList.remove(undoList.size() - 1); } } public void redo () { if (redoList.size() > 0) { ArrayList<DComponent> redoComponents = redoList.get(redoList.size() - 1); removeComponentList(redoComponents); redoList.remove(redoList.size() - 1); } } public void removeComponent (DComponent comp) { dirty = true; if (selectedComponent instanceof StartComponent) { listener.showMsg("This component cannot be deleted!", "Error", JOptionPane.ERROR_MESSAGE); return; } listener.changePropertyTableModel(null); comp.detachAll(); componentList.remove(comp); ArrayList<DComponent> tempList = new ArrayList<DComponent>(); tempList.add(comp); undoList.add(tempList); selectedComponent = null; } public void removeComponentList (ArrayList<DComponent> compList) { dirty = true; Iterator<DComponent> it = compList.iterator(); while (it.hasNext()) { DComponent comp = it.next(); if (comp instanceof StartComponent) { it.remove(); // listener.showMsg("Selection containts components that cannot be deleted!", "Error", JOptionPane.ERROR_MESSAGE); // compList.clear(); // return; } } for (DComponent comp : compList) comp.detachAllNotOnList(compList); componentList.removeAll(compList); undoList.add(new ArrayList<DComponent>(compList)); selectedComponent = null; selectedComponentsList.clear(); listener.changePropertyTableModel(null); } public void setProject (Project project) { this.project = project; } public boolean isDirty () { return dirty; } public void setDirty (boolean dirty) { this.dirty = dirty; } // ==================================================================INPUT============================================================================ @Override public boolean scrolled (int amount) { float newZoom = 0; if (amount == 1) // out { if (camera.zoom >= 4) return false; newZoom = camera.zoom + 0.1f * camera.zoom * 2; camera.position.x = Touch.getX() + (camera.zoom / newZoom) * (camera.position.x - Touch.getX()); camera.position.y = Touch.getY() + (camera.zoom / newZoom) * (camera.position.y - Touch.getY()); camera.zoom += 0.1f * camera.zoom * 2; } if (amount == -1) // in { if (camera.zoom <= 0.5f) return false; newZoom = camera.zoom - 0.1f * camera.zoom * 2; camera.position.x = Touch.getX() + (newZoom / camera.zoom) * (camera.position.x - Touch.getX()); camera.position.y = Touch.getY() + (newZoom / camera.zoom) * (camera.position.y - Touch.getY()); camera.zoom -= 0.1f * camera.zoom * 2; } return true; } // pan is worse because you must drag mouse a little bit to fire this event @Override public boolean pan (float x, float y, float deltaX, float deltaY) { if (selectedComponentsList.size() > 0 && selectedConnector == null) // FIXME gdy przesuwam kompoennty w lewo, one lekko przesuwaja sie w strone srodka, dlaczego? nie zawsze tak sie dzieje... { for (DComponent comp : selectedComponentsList) { comp.setX((int) (comp.getX() + deltaX * camera.zoom)); comp.setY((int) (comp.getY() - deltaY * camera.zoom)); } return true; } if (panCounter > 0) //this will igore moving camera when clicked somewhere with menu opened { if (selectedComponent == null && selectedConnector == null && selectedComponentsList.size() == 0) { if (Gdx.input.isButtonPressed(Buttons.RIGHT) && Gdx.input.isKeyPressed(Keys.CONTROL_LEFT) == false) { cameraDragged = true; camera.position.x = camera.position.x - deltaX * camera.zoom; camera.position.y = camera.position.y + deltaY * camera.zoom; return true; } } } else panCounter++; return false; } @Override public boolean touchUp (int screenX, int screenY, int pointer, int button) { if (button == Buttons.RIGHT && cameraDragged == false) { listener.mouseRightClicked(screenX, screenY); // ! we are sending raw coordinates return true; } cameraDragged = false; findConnection(screenX, screenY, true); return false; } @Override public boolean touchDragged (int screenX, int screenY, int pointer) { if (selectedComponentsList.size() > 0) { return false; } if (selectedComponent != null && selectedConnector == null && Gdx.input.isButtonPressed(Buttons.LEFT)) { selectedComponent.setX(Touch.calcX(screenX) - attachPointX); selectedComponent.setY(Touch.calcY(screenY) - attachPointY); return true; } return false; } @Override public boolean touchDown (float x, float y, int pointer, int button) { panCounter = 0; x = Touch.calcX(x); y = Touch.calcY(y); boolean found = false; for (DComponent comp : componentList) { if (comp.contains(x, y)) { dirty = true; if (selectedComponentsList.contains(comp)) { if (selectedComponent != null) return false; } else selectedComponentsList.clear(); selectedComponent = comp; attachPointX = (int) (x - selectedComponent.getX()); attachPointY = (int) (y - selectedComponent.getY()); found = true; listener.changePropertyTableModel(selectedComponent.getTableModel()); break; } } if (found == false) { selectedComponent = null; listener.changePropertyTableModel(null); selectedComponentsList.clear(); } else return true; return false; } @SuppressWarnings("unchecked") @Override public boolean keyDown (int keycode) { if (keycode == Keys.FORWARD_DEL) // because forward_del is delete key, del is backspace! { if (selectedComponent != null && selectedComponentsList.size() == 0) { removeComponent(selectedComponent); } if (selectedComponentsList.size() > 0) { removeComponentList(selectedComponentsList); } } if (keycode == Keys.BACKSPACE && selectedComponent != null) { dirty = true; selectedComponent.detachAll(); return true; } if (Gdx.input.isKeyPressed(Keys.CONTROL_LEFT) && Gdx.input.isKeyPressed(Keys.A)) { dirty = true; selectedComponentsList.clear(); selectedComponentsList.addAll(componentList); } if (selectedComponentsList.size() > 0 && Gdx.input.isKeyPressed(Keys.CONTROL_LEFT) && Gdx.input.isKeyPressed(Keys.C)) { dirty = true; clipboardList = new ArrayList<DComponent>(selectedComponentsList); } if (clipboardList != null && Gdx.input.isKeyPressed(Keys.CONTROL_LEFT) && Gdx.input.isKeyPressed(Keys.V)) { dirty = true; // TODO better positing system int x = clipboardList.get(0).getX(); // we will need this to position component later int y = clipboardList.get(0).getY(); // probably the best way to deep copy object is to serialize it and deserialize, we can use xstream for that String result = xstream.toXML(clipboardList); clipboardList = (ArrayList<DComponent>) xstream.fromXML(result); for (DComponent comp : clipboardList) // we need to asign new id's to components { if (comp.getClass() == StartComponent.class) { comp.detachAll(); continue; } comp.detachAllNotOnList(clipboardList); comp.setX(Touch.getX() + (comp.getX() - x)); // move new component to cursor pos comp.setY(Touch.getY() + (comp.getY() - y)); } componentList.addAll(clipboardList); } return false; } @Override public boolean mouseMoved (int screenX, int screenY) { findConnection(screenX, screenY, false); return false; } @Override public boolean tap (float x, float y, int count, int button) { return false; } @Override public boolean longPress (float x, float y) { // TODO Auto-generated method stub return false; } @Override public boolean fling (float velocityX, float velocityY, int button) { // TODO Auto-generated method stub return false; } @Override public boolean panStop (float x, float y, int pointer, int button) { // TODO Auto-generated method stub return false; } @Override public boolean zoom (float initialDistance, float distance) { // TODO Auto-generated method stub return false; } @Override public boolean pinch (Vector2 initialPointer1, Vector2 initialPointer2, Vector2 pointer1, Vector2 pointer2) { // TODO Auto-generated method stub return false; } @Override public boolean keyUp (int keycode) { // TODO Auto-generated method stub return false; } @Override public boolean keyTyped (char character) { return false; } @Override public boolean touchDown (int screenX, int screenY, int pointer, int button) { return false; } }