Hi, forum.
It has been a long time due to some unexpected issue, I might not be able finish this plan.
Some parts of this new api are kind of useable. I will post the source code below, anyone interested in this topic can test themselves.
import java.lang.reflect.Method;
import processing.core.PApplet;
public class PGestureHelper {
private PApplet p;
public float rangeX, rangeY;
public float xOffset, yOffset; //viewport offset
public float scale = 1f;
private Method tapMethod, dragMethod, longPressMethod, pinchMethod;
public float width,height; //actual
private float cWidth, cHeight; //canvas
private float oWidth, oHeight; //original
public PGestureHelper(PApplet parent, float canvasWidth, float canvasHeight) {
p = parent;
cWidth = canvasWidth;
cHeight = canvasHeight;
rangeX = (cWidth - p.width) / 2;
rangeY = (cHeight - p.height) / 2;
xOffset = -scaleFactor() * p.width / 2;
yOffset = -scaleFactor() * p.height / 2;
width = oWidth = p.width;
height = oHeight = p.height;
initialiseMethods();
}
private boolean longPressDetected = false;
private boolean locked = false;
private int pinches = 0; //>=2 valid
private float startX, startY;
public float dragX, dragY;
public float preMidX, preMidY;
private int touchTime;
private float[] preXY, curXY;
public void start() {
if (p.touches.length == 1) {
preXY = null;
pinches = 0;
startX = dragX = p.mouseX;
startY = dragY = p.mouseY;
}
if (locked) {return;}
locked = true;
touchTime = p.millis();
new Thread(() -> {
longPressDetected = false;
while (!longPressDetected && locked) {
if (p.millis() - touchTime >= 500) {
longPressDetected = true;
if (p.touches.length > 0 && PApplet.dist(startX, startY, p.mouseX, p.mouseY) < 36) {
call(longPressMethod, startX, startY);
}
locked = false;
}
}
}).start();
}
public void move() {
if (p.touches.length == 1) {
if (pinches > 0) {
//reset drag position
pinches = 0;
dragX = p.mouseX;
dragY = p.mouseY;
} else {
if (!call(dragMethod, p.mouseX, p.mouseY)) {
onDrag(p.mouseX, p.mouseY);
}
dragX = p.mouseX;
dragY = p.mouseY;
}
} else if (p.touches.length == 2) {
pinches++;
curXY = new float[]{p.touches[0].x, p.touches[0].y, p.touches[1].x, p.touches[1].y};
if (preXY == null) {
preXY = curXY;
return;
}
float midX = (curXY[0] + curXY[2]) / 2;
float midY = (curXY[1] + curXY[3]) / 2;
float dp = PApplet.dist(preXY[0], preXY[1], preXY[2], preXY[3]);
float dc = PApplet.dist(curXY[0], curXY[1], curXY[2], curXY[3]);
if (pinches > 2) {
if (!call(pinchMethod, midX, midY, dc - dp)) {
onPinch(midX, midY, dc - dp);
}
}
preMidX = midX;
preMidY = midY;
preXY = curXY;
}
}
public void end() {
if (!longPressDetected && p.millis() - touchTime <= 250) {
if (PApplet.dist(startX, startY, p.mouseX, p.mouseY) < 36) {
call(tapMethod, startX, startY);
}
locked = false;
}
}
//default drag method
private void onDrag(float x, float y) {
xOffset = PApplet.constrain(xOffset + (x - dragX) / scale, -rangeX - scaleFactor() * width, rangeX);
yOffset = PApplet.constrain(yOffset + (y - dragY) / scale, -rangeY - scaleFactor() * height, rangeY);
}
//default pinch method
private void onPinch(float x, float y, float d) {
float realX = toRealX(preMidX);
float realY = toRealY(preMidY);
scale = PApplet.constrain(scale + d / 250, 0.25f, 2.5f);
if (d < 0) {
width = (int) PApplet.min(scale * cWidth, width);
height = (int) PApplet.min(scale * cHeight, height);
} else {
width = (int) PApplet.min(scale * cWidth, oWidth);
height = (int) PApplet.min(scale * cHeight, oHeight);
}
rangeX = (cWidth - width) / 2;
rangeY = (cHeight - height) / 2;
xOffset = PApplet.constrain(x / scale - realX, -rangeX - scaleFactor() * width, rangeX);
yOffset = PApplet.constrain(y / scale - realY, -rangeY - scaleFactor() * height, rangeY);
}
@SuppressWarnings("ConstantConditions")
private boolean call(Method method, float... args) {
try {
if (args.length == 2) {
return (Boolean) method.invoke(p, args[0], args[1]);
} else if (args.length == 3) {
return (Boolean) method.invoke(p, args[0], args[1], args[2]);
}
} catch (Exception ignored) { }
return false;
}
private void initialiseMethods() {
try {
tapMethod = p.getClass().getMethod("onTap",
float.class, float.class);
dragMethod = p.getClass().getMethod("onDrag",
float.class, float.class);
longPressMethod = p.getClass().getMethod("onLongPress",
float.class, float.class);
pinchMethod = p.getClass().getMethod("onPinch",
float.class, float.class, float.class);
} catch (Exception ignored) { }
}
public float scaleFactor() { return (scale - 1) / scale;}
public float toRealX(float touchX) { return touchX / scale - xOffset;}
public float toRealY(float touchY) { return touchY / scale - yOffset;}
public void adjustView() {
p.scale(scale, scale);
p.translate(xOffset + (cWidth * scale < oWidth ? (oWidth / scale - cWidth) / 2 : 0),
yOffset + (cHeight * scale < oHeight ? (oHeight / scale - cHeight) / 2 : 0));
}
}
import java.text.DecimalFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import processing.core.PApplet;
public class PDebugInfo {
private Map<String, Integer> heights;
public final String fps = "fps";
private PApplet p;
private int x, y;
private int space = 50;
private float mTextSize = 30;
private int preS, preF, curF; // variables to calculate current fps
public PDebugInfo(PApplet parent) {
heights = new HashMap<>();
p = parent;
x = p.width - 225;
y = 50;
preS = Calendar.getInstance().get(Calendar.SECOND);
}
@SuppressWarnings("ConstantConditions")
public void display(String name, Number... value) {
float textSize = p.g.textSize;
p.textSize(mTextSize);
p.fill(0, 255, 0);
if (!heights.containsKey(name)) {
heights.put(name, y);
y += space;
}
switch (name) {
case fps:
p.text(name + ": " + fpsCur(), x, heights.get(name));
break;
default:
p.text(value.length == 1 ? name + ": " + new DecimalFormat(".00").format(value[0]) : "invalid arg(s)!", x, heights.get(name));
break;
}
p.noFill();
p.textSize(textSize);
}
public void setSpace(int space) {
this.space = space;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void setTextSize(float textSize) {
mTextSize = textSize;
}
private int fpsCur() {
if (Calendar.getInstance().get(Calendar.SECOND) != preS) {
preS = Calendar.getInstance().get(Calendar.SECOND);
curF = p.frameCount - preF;
preF = p.frameCount;
}
return curF;
}
}
import processing.core.PApplet;
public class PSketchMain extends PApplet {
private PGestureHelper gsh;
private PDebugInfo di;
public void settings() {
size(width, height,JAVA2D);
}
public void setup() {
float canvasWidth = 4000;
float canvasHeight = 4000;
gsh = new PGestureHelper(this, canvasWidth, canvasHeight);
di = new PDebugInfo(this);
background(255);
smooth(8);
}
public void draw() {
update();
background(255);
scale(1, 1);
displayDebugInfo();
//rect(0, 0, width, height);
strokeWeight(5);
//actual width range -rangeX ~ width + rangeX (canvasWidth - rangeX)
//actual height range -rangeY ~ height + rangeY (canvasHeight - rangeY)
gsh.adjustView();
rect(0 - gsh.rangeX, 0 - gsh.rangeY, 200, 200);
rect(gsh.width / 2f - 100, gsh.height / 2f - 100, 200, 200);
rect(gsh.width - 200 + gsh.rangeX, gsh.height - 200 + gsh.rangeY, 200, 200);
}
private void update() {
}
private void displayDebugInfo() {
di.display(di.fps);
di.display("xOffset", gsh.xOffset);
di.display("yOffset", gsh.yOffset);
di.display("scale", gsh.scale);
di.display("realX", mouseX / gsh.scale - gsh.xOffset + gsh.rangeX);
di.display("realY", mouseY / gsh.scale - gsh.yOffset + gsh.rangeY);
}
public void touchStarted() {
System.out.println("testmy start");
gsh.start();
}
public void touchMoved() {
System.out.println("testmy move");
gsh.move();
}
public void touchEnded() {
System.out.println("testmy end");
gsh.end();
}
public boolean onTap(float x, float y) {
return true; //no default method
}
public boolean onDrag(float x, float y) {
return false; //call default drag method
}
public boolean onLongPress(float x, float y) {
return true; //no default method
}
public boolean onPinch(float x, float y, float d) {
return false; //call default pinch method
}
}