package com.uwsoft.editor.view.ui; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.math.Circle; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.InputEvent; import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Pools; import com.kotcrab.vis.ui.widget.VisLabel; import com.uwsoft.editor.Overlap2DFacade; import com.uwsoft.editor.utils.Guide; import com.uwsoft.editor.view.stage.Sandbox; /** * Created by azakhary on 7/18/2015. */ public class RulersUI extends Actor { private static final String CLASS_NAME = "com.uwsoft.editor.view.ui.RulersUI"; public static final String ACTION_GUIDES_MODIFIED = CLASS_NAME + "ACTION_GUIDES_MODIFIED"; public static final String RIGHT_CLICK_RULER = CLASS_NAME + "RIGHT_CLICK_RULER"; private static final int rulerBoxSize = 14; private static final int topOffset = 49; private static final int leftOffset = 40; private static final int separatorsCount = 20; private static final Color BG_COLOR = new Color(48f/255f, 48f/255f, 48f/255f, 1f); private static final Color LINE_COLOR = new Color(85f/255f, 85f/255f, 85f/255f, 1f); private static final Color GUIDE_COLOR = new Color(255f/255f, 94f/255f, 0f/255f, 0.5f); private static final Color OVER_GUIDE_COLOR = new Color(255f/255f, 173f/255f, 125f/255f, 1f); private static final Color TEXT_COLOR = new Color(194f/255f, 194f/255f, 194f/255f, 1f); //Allows the ChangeRulerXPositionCommand to change the guide's position from the function UpdateGuideManully private static Guide editableDraggingGuide = null; private ShapeRenderer shapeRenderer; private Rectangle horizontalRect, verticalRect; private boolean isShowingPixels = false; private float viewMeasurableWidth; private float viewMeasurableHeight; private float gridMeasuringSize; private float gridMeasuringSizeInWorld; private float gridMeasureToDisplayScale; private Array<VisLabel> labels = new Array<>(); private Array<Guide> guides = new Array<>(); private Guide mouseOverGuide = null; private VisLabel guidePosLbl; private Guide draggingGuide = null; public RulersUI() { shapeRenderer = new ShapeRenderer(); horizontalRect = new Rectangle(); verticalRect = new Rectangle(); guidePosLbl = new VisLabel(); addListeners(); } private void addListeners() { addListener(new ClickListener() { private boolean isTouchingDownRuler; private boolean isTouchDownRulerVertical; @Override public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) { super.touchDown(event, x, y, pointer, button); Circle touchCircle = new Circle(); touchCircle.radius = 5; touchCircle.setPosition(x - Gdx.graphics.getWidth() / 2, y - Gdx.graphics.getHeight() / 2); isTouchingDownRuler = false; if(verticalRect.contains(touchCircle.x, touchCircle.y)) { isTouchDownRulerVertical = true; isTouchingDownRuler = true; } if(horizontalRect.contains(touchCircle.x, touchCircle.y)) { isTouchDownRulerVertical = false; isTouchingDownRuler = true; } // check for collision with guides. Guide collisionGuide = guideCollision(x, y); if(collisionGuide != null) { draggingGuide = collisionGuide; } if (button == 1) { editableDraggingGuide = draggingGuide; Overlap2DFacade.getInstance().sendNotification(RIGHT_CLICK_RULER); } return true; } @Override public void touchDragged(InputEvent event, float x, float y, int pointer) { super.touchDragged(event, x, y, pointer); Vector2 downPost = new Vector2(getTouchDownX(), getTouchDownY()); if (isTouchingDownRuler && draggingGuide == null && downPost.dst(x, y) > 3) { draggingGuide = new Guide(isTouchDownRulerVertical); guides.add(draggingGuide); } //Changes the dragging guide's position to the world position if(draggingGuide != null) { Vector2 worldCoords = hereToWorld(new Vector2(x-Gdx.graphics.getWidth()/2, y-Gdx.graphics.getHeight()/2)); if (draggingGuide.isVertical) { draggingGuide.pos = worldCoords.x; if (!isShowingPixels) snap(draggingGuide); } else { draggingGuide.pos = worldCoords.y; if (!isShowingPixels) { snap(draggingGuide); } } } } @Override public void touchUp(InputEvent event, float x, float y, int pointer, int button) { super.touchUp(event, x, y, pointer, button); if(getTapCount() >= 2) { // double click toggles the mode isShowingPixels = !isShowingPixels; } if(draggingGuide != null) { if((draggingGuide.isVertical && x-Gdx.graphics.getWidth()/2 < verticalRect.x+verticalRect.getWidth()) || (!draggingGuide.isVertical && y-Gdx.graphics.getHeight()/2 > horizontalRect.y)) { guides.removeValue(draggingGuide, true); } Overlap2DFacade.getInstance().sendNotification(ACTION_GUIDES_MODIFIED); } draggingGuide = null; } @Override public boolean mouseMoved(InputEvent event, float x, float y) { mouseOverGuide = guideCollision(x, y); return super.mouseMoved(event, x, y); } }); } @Override public void act(float delta) { if(!isVisible()) return; super.act(delta); horizontalRect.set(-Gdx.graphics.getWidth()/2+leftOffset, Gdx.graphics.getHeight()/2 - rulerBoxSize-topOffset, Gdx.graphics.getWidth()-leftOffset, rulerBoxSize); verticalRect.set(-Gdx.graphics.getWidth()/2+leftOffset, -Gdx.graphics.getHeight()/2, rulerBoxSize, Gdx.graphics.getHeight()-topOffset); //calculating sizes viewMeasurableWidth = Sandbox.getInstance().getViewport().getWorldWidth() * Sandbox.getInstance().getCamera().zoom; viewMeasurableHeight = Sandbox.getInstance().getViewport().getWorldHeight() * Sandbox.getInstance().getCamera().zoom; if(isShowingPixels) { viewMeasurableWidth = viewMeasurableWidth * Sandbox.getInstance().getPixelPerWU(); viewMeasurableHeight = viewMeasurableHeight * Sandbox.getInstance().getPixelPerWU(); } gridMeasureToDisplayScale = Gdx.graphics.getWidth()/viewMeasurableWidth; gridMeasuringSize = viewMeasurableWidth/separatorsCount; if(gridMeasuringSize <= 10) { gridMeasuringSize = Math.round(gridMeasuringSize); } else if (gridMeasuringSize > 10 && gridMeasuringSize <= 20) { gridMeasuringSize = Math.round(gridMeasuringSize/5)*5; } else { gridMeasuringSize = Math.round(gridMeasuringSize/10)*10; } gridMeasuringSizeInWorld = gridMeasuringSize; if (isShowingPixels) { gridMeasuringSizeInWorld = gridMeasuringSize/Sandbox.getInstance().getPixelPerWU(); } } private Vector2 worldToHere(Vector2 tmp) { tmp = Sandbox.getInstance().worldToScreen(tmp); tmp.x-=Gdx.graphics.getWidth()/2; tmp.y-=Gdx.graphics.getHeight()/2; return tmp; } private Vector2 hereToWorld(Vector2 tmp) { tmp.x+=Gdx.graphics.getWidth()/2; tmp.y+=Gdx.graphics.getHeight()/2; tmp = Sandbox.getInstance().screenToWorld(tmp); return tmp; } @Override public void draw(Batch batch, float parentAlpha) { batch.end(); OrthographicCamera uiCamera = (OrthographicCamera) getStage().getCamera(); Gdx.gl.glLineWidth(1.0f); Gdx.gl.glEnable(GL20.GL_BLEND); Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); shapeRenderer.setProjectionMatrix(uiCamera.projection); drawShapes(); Gdx.gl.glDisable(GL20.GL_BLEND); batch.begin(); batch.setColor(Color.WHITE); drawBatch(batch, parentAlpha); } public void drawShapes() { drawBg(); drawLines(); } public void drawBg() { shapeRenderer.begin(ShapeRenderer.ShapeType.Filled); shapeRenderer.setColor(BG_COLOR); shapeRenderer.rect(horizontalRect.x, horizontalRect.y, horizontalRect.width, horizontalRect.height); shapeRenderer.rect(verticalRect.x, verticalRect.y, verticalRect.width, verticalRect.height); shapeRenderer.end(); } public void drawLines() { shapeRenderer.begin(ShapeRenderer.ShapeType.Line); shapeRenderer.setColor(LINE_COLOR); // Static Lines for Aesthetics shapeRenderer.line(horizontalRect.x + rulerBoxSize, horizontalRect.y, horizontalRect.x + horizontalRect.width, horizontalRect.y); shapeRenderer.line(verticalRect.x + verticalRect.width+1, verticalRect.y, verticalRect.x + verticalRect.width+1, verticalRect.y + verticalRect.height - rulerBoxSize); //Functional lines to show grid Vector2 startPoint = new Vector2(horizontalRect.x + rulerBoxSize, verticalRect.y); Vector2 worldStartPoint = hereToWorld(startPoint); worldStartPoint.x -= worldStartPoint.x % gridMeasuringSizeInWorld; worldStartPoint.y -= worldStartPoint.y % gridMeasuringSizeInWorld; Vector2 worldStartPointCpy = new Vector2(worldStartPoint); Vector2 gridCurrPoint = worldToHere(worldStartPoint); labels.clear(); String postFix = ""; if(isShowingPixels) { postFix = "px"; worldStartPointCpy.x *= Sandbox.getInstance().getPixelPerWU(); worldStartPointCpy.y *= Sandbox.getInstance().getPixelPerWU(); } float gridSize = gridMeasuringSize*gridMeasureToDisplayScale; int iterator = 0; while(gridCurrPoint.x < horizontalRect.x+horizontalRect.getWidth()) { shapeRenderer.line(gridCurrPoint.x, horizontalRect.y, gridCurrPoint.x, horizontalRect.y+rulerBoxSize); shapeRenderer.line(gridCurrPoint.x+gridSize/2, horizontalRect.y, gridCurrPoint.x+gridSize/2, horizontalRect.y+rulerBoxSize/2); VisLabel label = Pools.obtain(VisLabel.class); label.setPosition(Gdx.graphics.getWidth()/2 + gridCurrPoint.x+2, Gdx.graphics.getHeight()/2 + horizontalRect.y+7); label.setColor(TEXT_COLOR); label.setText((int)Math.abs(worldStartPointCpy.x + iterator * gridMeasuringSize)+postFix); labels.add(label); gridCurrPoint.x+=gridSize; iterator++; } iterator = 0; while(gridCurrPoint.y < verticalRect.y+verticalRect.getHeight()) { shapeRenderer.line(verticalRect.x+verticalRect.getWidth(), gridCurrPoint.y, verticalRect.x+verticalRect.getWidth()-rulerBoxSize, gridCurrPoint.y); shapeRenderer.line(verticalRect.x+verticalRect.getWidth(), gridCurrPoint.y+gridSize/2, verticalRect.x+verticalRect.getWidth()-rulerBoxSize/2, gridCurrPoint.y+gridSize/2); VisLabel label = Pools.obtain(VisLabel.class); label.setColor(TEXT_COLOR); String lblText = (int)Math.abs(worldStartPointCpy.y + iterator * gridMeasuringSize)+""; lblText = verticalize(lblText); label.setText(lblText); label.setWrap(true); label.setPosition(Gdx.graphics.getWidth()/2 + verticalRect.x+3, Gdx.graphics.getHeight()/2 + gridCurrPoint.y - label.getPrefHeight()/2); labels.add(label); gridCurrPoint.y+=gridSize; iterator++; } drawGuides(); shapeRenderer.end(); } public void drawGuides() { for(int i = 0; i < guides.size; i++) { Guide guide = guides.get(i); if(mouseOverGuide == guide) { shapeRenderer.setColor(OVER_GUIDE_COLOR); } else { shapeRenderer.setColor(GUIDE_COLOR); } if(guide.isVertical) { Vector2 localCoords = worldToHere(new Vector2(guide.pos, 0)); if(localCoords.x > verticalRect.x+verticalRect.width) { shapeRenderer.line(localCoords.x, -Gdx.graphics.getHeight() / 2, localCoords.x, horizontalRect.y); } } else { Vector2 localCoords = worldToHere(new Vector2(0, guide.pos)); if(localCoords.y < horizontalRect.y) { shapeRenderer.line(verticalRect.x + verticalRect.getWidth(), localCoords.y, Gdx.graphics.getWidth(), localCoords.y); } } } } public void drawBatch(Batch batch, float parentAlpha) { for(int i = 0; i < labels.size; i++) { labels.get(i).draw(batch, parentAlpha); Pools.free(labels.get(i)); } if(draggingGuide != null) { float pos = draggingGuide.pos; String axis = "Y"; String postfix = ""; if(draggingGuide.isVertical) axis = "X"; if(isShowingPixels) { pos = draggingGuide.pos * Sandbox.getInstance().getPixelPerWU(); postfix="px"; } //Rounds the guide's position to the nearest 100th, if in World Unit mode String positionAsString = "" + pos; if (!isShowingPixels) { pos = (float) Math.round(pos * 100) / 100; positionAsString = String.format("%.2f", pos); } else pos = (float) (Math.round(pos * 100)/100); guidePosLbl.setText(axis+": "+ positionAsString + postfix); guidePosLbl.setPosition(Gdx.input.getX()+15, Gdx.graphics.getHeight() - Gdx.input.getY()+15); guidePosLbl.draw(batch, parentAlpha); } } private String verticalize(String text) { String newText = ""; for(int i = 0; i < text.length(); i++) { newText += text.charAt(i) + "\n"; } return newText; } @Override public Actor hit (float x, float y, boolean touchable) { if(verticalRect.contains(x-Gdx.graphics.getWidth()/2, y-Gdx.graphics.getHeight()/2) || horizontalRect.contains(x-Gdx.graphics.getWidth()/2, y-Gdx.graphics.getHeight()/2)) { return this; } mouseOverGuide = guideCollision(x, y); if(mouseOverGuide != null) { return this; } return null; } public Guide guideCollision(float x, float y) { Vector2 point = new Vector2(x-Gdx.graphics.getWidth()/2, y-Gdx.graphics.getHeight()/2); point = hereToWorld(point); Circle touchCircle = new Circle(); touchCircle.radius = 3f/Sandbox.getInstance().getPixelPerWU(); touchCircle.setPosition(point.x, point.y); for(int i = 0; i < guides.size; i++) { if(guides.get(i).isVertical) { // this is really weird that I have to substract half of radius.... I am totally lost. if(touchCircle.contains(guides.get(i).pos- touchCircle.radius/2f, touchCircle.y)) { return guides.get(i); } } else { if(touchCircle.contains(touchCircle.x, guides.get(i).pos- touchCircle.radius/2f)) { return guides.get(i); } } } return null; } //Snaps to nearest quarter if less than 0.04 World Units away private void snap(Guide guide) { float snapDistance = 0.04f; float absoluteValPos = Math.abs(guide.pos); float nearestQuarter = Math.round(absoluteValPos * 4) / 4f; if (Math.abs(absoluteValPos - nearestQuarter) < snapDistance) { absoluteValPos = nearestQuarter; } if (guide.pos < 0) absoluteValPos *= -1; guide.pos = absoluteValPos; } public static Guide getPreviousGuide() { return editableDraggingGuide; } //Allows the ChangeRulerXPositionCommand to change the guide's position public static void updateGuideManually(float destination) { editableDraggingGuide.pos = destination; } public Array<Guide> getGuides() { return guides; } public void setGuides(Array<Guide> guides) { this.guides = guides; } }