mini-cards/app/src/main/java/com/codigoparallevar/minicards/CanvasView.java
2018-01-24 23:55:58 +01:00

410 lines
13 KiB
Java

package com.codigoparallevar.minicards;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import com.codigoparallevar.minicards.motion.MotionMode;
import com.codigoparallevar.minicards.types.PartConnection;
import com.codigoparallevar.minicards.types.connectors.input.InputConnector;
import com.codigoparallevar.minicards.types.connectors.input.SignalInputConnector;
import com.codigoparallevar.minicards.types.connectors.output.OutputConnector;
import com.codigoparallevar.minicards.types.Part;
import com.codigoparallevar.minicards.types.PartGrid;
import com.codigoparallevar.minicards.types.Position;
import com.codigoparallevar.minicards.types.Selectable;
import com.codigoparallevar.minicards.types.Tuple2;
import com.codigoparallevar.minicards.types.Tuple4;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class CanvasView extends View implements PartGrid {
@NonNull
List<Part> parts = new ArrayList<>();
@Nullable
Selectable selectedPart;
@Nullable
final Position lastTouchedPosition = new Position();
@Nullable
long lastTouchedTime;
@Nullable
private MotionMode.Type motionMode;
@NonNull
private String name = "default";
private final static float touchTimeForLongTouchInMillis = 500;
private boolean _isDragging = false;
private CardActivity parentActivity = null;
private Tuple4<Integer, Integer, Integer, Integer> _dropToRemoveZone = new Tuple4<>(0, 0, 0, 0);
private boolean _devMode = false;
@NonNull
private Tuple2<Integer, Integer> _viewOrigin = new Tuple2<>(0, 0);
@Nullable
private Tuple2<Integer, Integer> _mouseDownPoint = null;
private int cardBackgroundColor;
public CanvasView(Context context) {
super(context);
}
public CanvasView(Context context, AttributeSet attr) {
super(context, attr);
}
public void loadCard(String path) throws IOException, ErrorLoadingCardException {
CardFile card = CardFile.load(path, this);
name = card.getName();
setCardBackgroundColor(card.getBackgroundColor());
parts = card.getParts();
List<PartConnection> connections = card.getConnections();
resolveConnections(connections);
}
// This might not be needed here, and probably could be moved to the CardFile.
private void resolveConnections(List<PartConnection> connections) {
Map<String, Part> partsById = buildPartsById();
for (PartConnection connection : connections){
if (!partsById.containsKey(connection.inputPartId)){
Log.e("Canvas view", "Key '" + connection.inputPartId
+ "' not found on deserialization");
continue;
}
Part inputPart = partsById.get(connection.inputPartId);
InputConnector inputConnector = inputPart.getConnectorWithId(connection.inputConnectorId);
if (inputConnector == null){
Log.e("Canvas view", "Connector ID '" + connection.inputConnectorId
+ "' not found on deserialization");
continue;
}
OutputConnector outputConnector = connection.outputConnector;
outputConnector.connectTo(inputConnector);
}
}
private Map<String, Part> buildPartsById() {
Map<String, Part> partsById = new HashMap<>(parts.size());
for (Part part : parts) {
partsById.put(part.get_id(), part);
Log.w("CanvasView", "Added part ID: " + part.get_id() + " - " + part);
}
return partsById;
}
@Override
public void onDraw(Canvas canvas){
final long renderStartTime = System.currentTimeMillis();
ScrolledCanvas scrolledCanvas = new ScrolledCanvas(canvas, _viewOrigin);
drawBackground(scrolledCanvas);
for (Part part : parts){
part.draw(scrolledCanvas, _devMode);
}
Log.d("Render time", System.currentTimeMillis() - renderStartTime + "ms");
}
private void drawBackground(ScrolledCanvas canvas) {
if (!_devMode){
return;
}
// Blueprint background
final int width = getWidth() + getLeft();
final int height = getHeight() + getTop();
final int cellSize = 50;
Paint blueprintLines = new Paint(Paint.ANTI_ALIAS_FLAG);
blueprintLines.setColor(Color.argb(100, 255, 255, 255));
// Vertical lines
for (int x = cellSize; x < width; x += cellSize){
canvas.drawLine(x, 0, x, height, blueprintLines);
}
// Horizontal lines
for (int y = cellSize; y < height; y += cellSize){
canvas.drawLine(0, y, width, y, blueprintLines);
}
}
@Override
public boolean onTouchEvent(MotionEvent event){
final int xInScreen = (int) event.getX() - this.getLeft();
final int yInScreen = (int) event.getY() - this.getTop();
final int xInCanvas = xInScreen + _viewOrigin.item1;
final int yInCanvas = yInScreen + _viewOrigin.item2;
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
{
_mouseDownPoint = new Tuple2<>(xInScreen, yInScreen);
selectedPart = getPartOn(xInCanvas, yInCanvas);
lastTouchedPosition.to(xInScreen, yInScreen);
lastTouchedTime = System.currentTimeMillis();
if (selectedPart == null) {
Log.d("Touched part", "not found");
return false;
}
Log.d("Touched part", "Part: " + selectedPart);
}
break;
case MotionEvent.ACTION_UP:
{
if (motionMode == null){
motionMode = getMotionMode(false);
}
if (motionMode != MotionMode.Type.LongTouch) {
if (selectedPart != null){
if (selectedPart instanceof Part){
((Part) selectedPart).touched();
}
}
}
else if (motionMode == MotionMode.Type.LongTouch && _devMode) {
if (selectedPart != null) {
selectedPart.getMoveable().drop(xInCanvas, yInCanvas);
if (inDropZone(xInScreen, yInScreen)) {
Log.d("Canvas", "Deleting element" + selectedPart);
parts.remove(selectedPart);
selectedPart.unlink();
}
invalidate();
}
}
_isDragging = false;
motionMode = null;
selectedPart = null;
}
try {
saveState();
} catch (IOException e) {
Log.w("PartCanvasView", e.getMessage());
}
break;
case MotionEvent.ACTION_MOVE:
{
if (!_devMode) {
break;
}
if (selectedPart == null){
int xMovement = _mouseDownPoint.item1 - xInScreen;
int yMovement = _mouseDownPoint.item2 - yInScreen;
_viewOrigin = new Tuple2(
_viewOrigin.item1 + xMovement,
_viewOrigin.item2 + yMovement);
_mouseDownPoint = new Tuple2(xInScreen, yInScreen);
}
else {
Log.i("Canvas", "X: " + xInScreen + " Y: " + yInScreen
+ " in drop zone " + _dropToRemoveZone + " : "
+ inDropZone(xInScreen, yInScreen));
if (motionMode == null) {
final Selectable nowSelectedPart = getPartOn(xInCanvas, yInCanvas);
final boolean canWait = selectedPart == nowSelectedPart;
motionMode = getMotionMode(canWait);
}
if (motionMode == MotionMode.Type.LongTouch) {
if (selectedPart != null) {
_isDragging = true;
selectedPart.getMoveable().moveEnd(xInCanvas, yInCanvas);
invalidate();
}
}
}
}
break;
default:
{
Log.d("PartCanvasView", "Unhandled action: " + event.getAction());
}
}
invalidate();
if (parentActivity != null) {
parentActivity.invalidate();
}
return true;
}
private boolean inDropZone(int x, int y) {
return (x >= _dropToRemoveZone._x1) && (x <= _dropToRemoveZone._x2)
&& (y >= _dropToRemoveZone._y1) && (y <= _dropToRemoveZone._y2);
}
private void saveState() throws IOException {
CardFile cardFile = new CardFile(CardFile.getDefaultCardStorage(getContext()));
cardFile.setName(name);
cardFile.addParts(parts);
cardFile.setBackgroundColor(getCardBackgroundColor());
cardFile.save(getContext());
}
@Nullable
private MotionMode.Type getMotionMode(boolean canWait) {
if (selectedPart == null){
return MotionMode.Type.Displacement;
}
if ((System.currentTimeMillis() - lastTouchedTime) >= touchTimeForLongTouchInMillis){
return MotionMode.Type.LongTouch;
}
if (canWait) {
return null;
}
return MotionMode.Type.ShortTouch;
}
@Nullable
public Selectable getPartOn(int x, int y) {
// Look in the list of parts, in reverse so top-most elements are checked before
for (int i = parts.size() - 1; i >= 0; i--){
final Part part = parts.get(i);
if (part.containsPoint(x, y)){
return part;
}
}
// If no part was found, do the same for connectors
for (int i = parts.size() - 1; i >= 0; i--){
final Part part = parts.get(i);
// First try with output connectors
for (OutputConnector outputConnector : part.getOutputConnectors()){
if (outputConnector.containsPoint(x, y)){
return outputConnector;
}
}
// Then with input ones
for (InputConnector inputConnector : part.getSignalInputConnectors()){
if (inputConnector.containsPoint(x, y)){
return inputConnector;
}
}
}
return null;
}
@Override
@Nullable
public SignalInputConnector getSignalInputConnectorOn(int x, int y) {
// If no part was found, do the same for connectors
for (int i = parts.size() - 1; i >= 0; i--){
final Part part = parts.get(i);
// Then with input ones
for (SignalInputConnector inputConnector : part.getSignalInputConnectors()){
if (inputConnector.containsPoint(x, y)){
return inputConnector;
}
}
}
return null;
}
public void addPart(Part part) {
parts.add(part);
invalidate();
}
public boolean isDragging() {
return _isDragging;
}
public void setParentActivity(CardActivity parentActivity) {
this.parentActivity = parentActivity;
}
public void setDropZone(float x1, float x2, float y1, float y2) {
_dropToRemoveZone = new Tuple4<>((int) x1, (int) x2, (int) y1, (int) y2);
}
public void setDevMode(boolean devMode) {
_devMode = devMode;
if (!_devMode) {
CenterView();
}
this.invalidate();
}
public void CenterView() {
this._viewOrigin = new Tuple2<>(0, 0);
this.invalidate();
}
public Tuple2<Integer, Integer> getCenteredOn() {
int xOffset = _viewOrigin.item1 + getWidth() / 2;
int yOffset = _viewOrigin.item2 + getHeight() / 2;
return new Tuple2<>(xOffset, yOffset);
}
@Override
public void update() {
parentActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
CanvasView.this.invalidate();
}
});
}
public int getCardBackgroundColor() {
return cardBackgroundColor;
}
public void setCardBackgroundColor(int backgroundColor) {
this.cardBackgroundColor = backgroundColor;
setBackgroundColor(backgroundColor);
}
public void resume() {
for (Part part : parts) {
part.resume();
}
}
public void pause() {
for (Part part : parts) {
part.resume();
}
}
}