brouter/brouter-routing-app/src/main/java/btools/routingapp/BInstallerView.java
2023-07-07 18:32:11 +02:00

330 lines
10 KiB
Java

package btools.routingapp;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
public class BInstallerView extends View {
public static final int MASK_SELECTED_RD5 = 1;
public static final int MASK_DELETED_RD5 = 2;
public static final int MASK_INSTALLED_RD5 = 4;
public static final int MASK_CURRENT_RD5 = 8;
private static final float SCALE_GRID_VISIBLE = 3;
private final Bitmap bmp;
private final float[] testVector = new float[2];
private final int[] tileStatus;
private final Matrix mat;
private final GestureDetector mGestureDetector;
private final ScaleGestureDetector mScaleGestureDetector;
Paint paintGrid = new Paint();
Paint paintTiles = new Paint();
private float viewscale;
private boolean tilesVisible = false;
private OnSelectListener mOnSelectListener;
public BInstallerView(Context context, AttributeSet attrs) {
super(context, attrs);
try {
AssetManager assetManager = getContext().getAssets();
InputStream istr = assetManager.open("world.png");
bmp = BitmapFactory.decodeStream(istr);
istr.close();
} catch (IOException io) {
throw new RuntimeException("cannot read world.png from assets");
}
tileStatus = new int[72 * 36];
mat = new Matrix();
mGestureDetector = new GestureDetector(context, new GestureListener());
mScaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureListener());
}
public void setOnSelectListener(OnSelectListener listener) {
mOnSelectListener = listener;
}
private void setRatio(float ratio, float focusX, float focusY) {
if (currentScale() * ratio >= 1) {
mat.postScale(ratio, ratio, focusX, focusY);
fitBounds();
boolean landscape = getWidth() > getHeight();
tilesVisible = currentScale() >= (landscape ? SCALE_GRID_VISIBLE: SCALE_GRID_VISIBLE-1);
invalidate();
}
}
private void setScale(float scale, float focusX, float focusY) {
float ratio = scale / currentScale();
setRatio(ratio, focusX, focusY);
}
public void setTileStatus(int tileIndex, int tileMask) {
if (mOnSelectListener == null) {
return;
}
tileStatus[tileIndex] |= tileMask;
mOnSelectListener.onSelect();
invalidate();
}
public void toggleTileStatus(int tileIndex, int tileMask) {
if (mOnSelectListener == null) {
return;
}
tileStatus[tileIndex] ^= tileMask;
mOnSelectListener.onSelect();
invalidate();
}
public void clearAllTilesStatus(int tileMask) {
for (int ix = 0; ix < 72; ix++) {
for (int iy = 0; iy < 36; iy++) {
int tileIndex = gridPos2Tileindex(ix, iy);
tileStatus[tileIndex] ^= tileStatus[tileIndex] & tileMask;
}
}
if (mOnSelectListener != null) {
mOnSelectListener.onSelect();
}
invalidate();
}
public ArrayList<Integer> getSelectedTiles(int tileMask) {
ArrayList<Integer> selectedTiles = new ArrayList<>();
for (int ix = 0; ix < 72; ix++) {
for (int iy = 0; iy < 36; iy++) {
int tileIndex = gridPos2Tileindex(ix, iy);
if ((tileStatus[tileIndex] & tileMask) != 0 && BInstallerSizes.getRd5Size(tileIndex) > 0) {
selectedTiles.add(tileIndex);
}
}
}
return selectedTiles;
}
private int gridPos2Tileindex(int ix, int iy) {
return (35 - iy) * 72 + (ix >= 70 ? ix - 70 : ix + 2);
}
private int tileIndex(float x, float y) {
int ix = (int) (72.f * x / bmp.getWidth());
int iy = (int) (36.f * y / bmp.getHeight());
if (ix >= 0 && ix < 72 && iy >= 0 && iy < 36) return gridPos2Tileindex(ix, iy);
return -1;
}
// get back the current image scale
private float currentScale() {
testVector[0] = 1.f;
mat.mapVectors(testVector);
return testVector[0] / viewscale;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
int imgwOrig = getWidth();
int imghOrig = getHeight();
float scaleX = imgwOrig / ((float) bmp.getWidth());
float scaleY = imghOrig / ((float) bmp.getHeight());
viewscale = Math.max(scaleX, scaleY);
mat.preScale(viewscale, viewscale, bmp.getWidth() /2f, 0);
setRatio(1f, bmp.getWidth() /2f, bmp.getHeight() /2f);
tilesVisible = false;
}
@Override
protected void onDraw(Canvas canvas) {
canvas.setMatrix(mat);
canvas.drawBitmap(bmp, 0, 0, null);
// draw 5*5 lattice starting at scale=3
int iw = bmp.getWidth();
int ih = bmp.getHeight();
float fw = iw / 72.f;
float fh = ih / 36.f;
if (tilesVisible) {
paintGrid.setColor(Color.GREEN);
paintGrid.setStyle(Paint.Style.STROKE);
for (int ix = 0; ix < 72; ix++) {
for (int iy = 0; iy < 36; iy++) {
int tidx = gridPos2Tileindex(ix, iy);
int tilesize = BInstallerSizes.getRd5Size(tidx);
if (tilesize > 0) {
canvas.drawRect(fw * ix, fh * (iy + 1), fw * (ix + 1), fh * iy, paintGrid);
}
}
}
int mask2 = MASK_SELECTED_RD5 | MASK_DELETED_RD5 | MASK_INSTALLED_RD5;
int mask3 = mask2 | MASK_CURRENT_RD5;
paintTiles.setStyle(Paint.Style.STROKE);
paintTiles.setColor(Color.GRAY);
paintTiles.setStrokeWidth(1);
drawSelectedTiles(canvas, paintTiles, fw, fh, MASK_INSTALLED_RD5, mask3);
paintTiles.setColor(Color.BLUE);
paintTiles.setStrokeWidth(1);
drawSelectedTiles(canvas, paintTiles, fw, fh, MASK_INSTALLED_RD5 | MASK_CURRENT_RD5, mask3);
paintTiles.setColor(Color.GREEN);
paintTiles.setStrokeWidth(2);
drawSelectedTiles(canvas, paintTiles, fw, fh, MASK_SELECTED_RD5, mask2);
paintTiles.setColor(Color.YELLOW);
paintTiles.setStrokeWidth(2);
drawSelectedTiles(canvas, paintTiles, fw, fh, MASK_SELECTED_RD5 | MASK_INSTALLED_RD5, mask2);
paintTiles.setColor(Color.RED);
paintTiles.setStrokeWidth(2);
drawSelectedTiles(canvas, paintTiles, fw, fh, MASK_DELETED_RD5 | MASK_INSTALLED_RD5, mask2);
}
}
private void drawSelectedTiles(Canvas canvas, Paint pnt, float fw, float fh, int status, int mask) {
for (int ix = 0; ix < 72; ix++)
for (int iy = 0; iy < 36; iy++) {
int tidx = gridPos2Tileindex(ix, iy);
if ((tileStatus[tidx] & mask) == status) {
int tilesize = BInstallerSizes.getRd5Size(tidx);
if (tilesize > 0) {
// draw cross
canvas.drawLine(fw * ix, fh * iy, fw * (ix + 1), fh * (iy + 1), pnt);
canvas.drawLine(fw * ix, fh * (iy + 1), fw * (ix + 1), fh * iy, pnt);
// draw frame
canvas.drawRect(fw * ix, fh * (iy + 1), fw * (ix + 1), fh * iy, pnt);
}
}
}
}
private void fitBounds() {
float[] srcPoints = new float[]{
0, 0,
bmp.getWidth(), bmp.getHeight()
};
float[] dstPoints = new float[srcPoints.length];
float transX = 0;
float transY = 0;
mat.mapPoints(dstPoints, srcPoints);
if (dstPoints[0] > 0) {
transX = -dstPoints[0];
} else if (dstPoints[2] < getWidth()) {
transX = getWidth() - dstPoints[2];
}
if (dstPoints[1] > 0) {
transY = -dstPoints[1];
} else if (dstPoints[3] < getHeight()) {
transY = getHeight() - dstPoints[3];
}
if (transX != 0 || transY != 0) {
mat.postTranslate(transX, transY);
}
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean retVal = mScaleGestureDetector.onTouchEvent(event);
retVal = mGestureDetector.onTouchEvent(event) || retVal;
return retVal || super.onTouchEvent(event);
}
interface OnSelectListener {
void onSelect();
}
class GestureListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
if (tilesVisible) {
Matrix imat = new Matrix();
if (mat.invert(imat)) {
float[] touchPoint = {e.getX(), e.getY()};
imat.mapPoints(touchPoint);
int tidx = tileIndex(touchPoint[0], touchPoint[1]);
if (tidx != -1) {
if ((tileStatus[tidx] & MASK_SELECTED_RD5) != 0) {
toggleTileStatus(tidx, MASK_SELECTED_RD5);
if ((tileStatus[tidx] & MASK_INSTALLED_RD5) != 0) {
setTileStatus(tidx, MASK_DELETED_RD5);
}
} else if ((tileStatus[tidx] & MASK_DELETED_RD5) != 0) {
toggleTileStatus(tidx, MASK_DELETED_RD5);
} else {
toggleTileStatus(tidx, MASK_SELECTED_RD5);
}
}
}
invalidate();
}
return true;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
if (!tilesVisible) {
setScale(4, e.getX(), e.getY());
} else {
setScale(1, e.getX(), e.getY());
}
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
mat.postTranslate(-distanceX, -distanceY);
fitBounds();
invalidate();
return true;
}
}
class ScaleGestureListener implements ScaleGestureDetector.OnScaleGestureListener {
@Override
public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
return true;
}
@Override
public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
float focusX = scaleGestureDetector.getFocusX();
float focusY = scaleGestureDetector.getFocusY();
float ratio = scaleGestureDetector.getScaleFactor();
setRatio(ratio, focusX, focusY);
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) {
}
}
}