Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,10 @@ dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "androidx.print:print:1.0.0"
implementation 'androidx.appcompat:appcompat:1.6.1'

// Test dependencies
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test:rules:1.5.0'
androidTestImplementation 'junit:junit:4.13.2'
}
31 changes: 29 additions & 2 deletions app/src/main/java/org/libsdl/app/SDLSurface.java
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,36 @@ public boolean onKey(View v, int keyCode, KeyEvent event) {
@Override
public boolean onTouch(View v, MotionEvent event) {
/* Ref: http://developer.android.com/training/gestures/multi.html */
int touchDevId = event.getDeviceId();
final int pointerCount = event.getPointerCount();

// Forward ALL pointer data to native multitouch handler
int action = event.getActionMasked();
int pointerCount = event.getPointerCount();

if (pointerCount > 0) {
float[] x = new float[pointerCount];
float[] y = new float[pointerCount];
long[] pointerIds = new long[pointerCount];

for (int i = 0; i < pointerCount; i++) {
x[i] = event.getX(i);
y[i] = event.getY(i);
pointerIds[i] = event.getPointerId(i);
}

// Call our native multitouch handler
try {
Class<?> activityClass = Class.forName("org.tuxpaint.tuxpaintActivity");
java.lang.reflect.Method method = activityClass.getDeclaredMethod(
"forwardMultitouchToNative", int.class, int.class, float[].class, float[].class, long[].class);
method.setAccessible(true);
method.invoke(null, action, pointerCount, x, y, pointerIds);
} catch (Exception e) {
// Silently ignore if method not found (for non-tuxpaint apps)
}
}

int touchDevId = event.getDeviceId();
// action and pointerCount already defined above
int pointerFingerId;
int i = -1;
float x,y,p;
Expand Down
31 changes: 31 additions & 0 deletions app/src/main/java/org/tuxpaint/tuxpaintActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,20 @@
import android.util.Log;
import android.content.res.AssetManager;
import android.content.pm.PackageManager;
import android.view.MotionEvent;
import android.Manifest;

public class tuxpaintActivity extends SDLActivity {
private static final String TAG = "Tux Paint";
private static AssetManager mgr;
private static native boolean managertojni(AssetManager mgr);
private static native void setnativelibdir(String path);
private static native void nativeOnTouch(int action, int pointerCount, float[] x, float[] y, long[] pointerIds);

// Called by SDLSurface via reflection to forward all multitouch data
public static void forwardMultitouchToNative(int action, int pointerCount, float[] x, float[] y, long[] pointerIds) {
nativeOnTouch(action, pointerCount, x, y, pointerIds);
}


@Override
Expand All @@ -38,6 +45,30 @@ protected void onCreate(Bundle savedInstanceState) {
setnativelibdir(getApplicationInfo().nativeLibraryDir + "/");
}

@Override
public boolean onTouchEvent(MotionEvent event) {
// Forward multitouch data to native code
int action = event.getActionMasked();
int pointerCount = event.getPointerCount();

if (pointerCount > 0) {
float[] x = new float[pointerCount];
float[] y = new float[pointerCount];
long[] pointerIds = new long[pointerCount];

for (int i = 0; i < pointerCount; i++) {
x[i] = event.getX(i);
y[i] = event.getY(i);
pointerIds[i] = event.getPointerId(i);
}

nativeOnTouch(action, pointerCount, x, y, pointerIds);
}

// Let SDL handle it too
return super.onTouchEvent(event);
}

static {
System.loadLibrary("c++_shared");
System.loadLibrary("tuxpaint_png");
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/jni/Application.mk
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
APP_ABI := arm64-v8a
APP_PLATFORM := android-16
APP_CFLAGS += -Wno-error=format-security
APP_CFLAGS += -Wno-error=format-security -Wno-error -O3 -DENABLE_MULTITOUCH
APP_STL := c++_shared
APP_CPPFLAGS += -fexceptions
APP_ALLOW_MISSING_DEPS := true
Expand Down
1 change: 1 addition & 0 deletions app/src/main/jni/tuxpaint/Android.mk
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ LOCAL_SRC_FILES := \
src/onscreen_keyboard.c \
src/android_print.c \
src/android_mbstowcs.c \
src/android_multitouch.c \
src/fill.c \
src/android_assets.c \
src/gifenc.c\
Expand Down
121 changes: 121 additions & 0 deletions app/src/main/jni/tuxpaint/src/android_multitouch.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
android_multitouch.c

Android-specific multitouch handling via JNI
Receives MotionEvents directly from Java and makes them available to tuxpaint.c
*/

#include <jni.h>
#include <android/log.h>
#include <string.h>

#define TAG "TuxPaint_Multitouch"
#define MAX_POINTERS 10

// Global state for multitouch tracking
typedef struct {
int active;
long pointer_id;
float x;
float y;
float last_x;
float last_y;
} AndroidPointerState;

static AndroidPointerState g_pointers[MAX_POINTERS];
static int g_pointer_count = 0;
static int g_initialized = 0;

// Initialize multitouch state
void android_multitouch_init() {
if (!g_initialized) {
memset(g_pointers, 0, sizeof(g_pointers));
g_pointer_count = 0;
g_initialized = 1;
__android_log_print(ANDROID_LOG_INFO, TAG, "Android multitouch initialized");
}
}

// Get current pointer count
int android_multitouch_get_count() {
return g_pointer_count;
}

// Get pointer data by index
int android_multitouch_get_pointer(int index, long *out_id, float *out_x, float *out_y, float *out_last_x, float *out_last_y) {
if (index < 0 || index >= MAX_POINTERS || !g_pointers[index].active) {
return 0;
}

if (out_id) *out_id = g_pointers[index].pointer_id;
if (out_x) *out_x = g_pointers[index].x;
if (out_y) *out_y = g_pointers[index].y;
if (out_last_x) *out_last_x = g_pointers[index].last_x;
if (out_last_y) *out_last_y = g_pointers[index].last_y;

return 1;
}

// JNI function called from Java
JNIEXPORT void JNICALL
Java_org_tuxpaint_tuxpaintActivity_nativeOnTouch(JNIEnv *env, jclass cls,
jint action, jint pointerCount,
jfloatArray x, jfloatArray y,
jlongArray pointerIds) {
if (!g_initialized) {
android_multitouch_init();
}

// Get array data
jfloat *x_arr = (*env)->GetFloatArrayElements(env, x, NULL);
jfloat *y_arr = (*env)->GetFloatArrayElements(env, y, NULL);
jlong *id_arr = (*env)->GetLongArrayElements(env, pointerIds, NULL);

// Update global state
g_pointer_count = pointerCount;

for (int i = 0; i < pointerCount && i < MAX_POINTERS; i++) {
// Check if this is a NEW pointer (different ID or wasn't active)
int is_new_pointer = (!g_pointers[i].active || g_pointers[i].pointer_id != id_arr[i]);

g_pointers[i].active = 1;
g_pointers[i].pointer_id = id_arr[i];

if (is_new_pointer) {
// First touch: Initialize both current AND last position to same value
// This prevents drawing from old position to new position
g_pointers[i].x = x_arr[i];
g_pointers[i].y = y_arr[i];
g_pointers[i].last_x = x_arr[i]; // Same as current position!
g_pointers[i].last_y = y_arr[i];
} else {
// Continuing touch: Save previous position, then update
g_pointers[i].last_x = g_pointers[i].x;
g_pointers[i].last_y = g_pointers[i].y;
g_pointers[i].x = x_arr[i];
g_pointers[i].y = y_arr[i];
}
}

// Clear inactive pointers
for (int i = pointerCount; i < MAX_POINTERS; i++) {
g_pointers[i].active = 0;
}

// Log for debugging
static int log_counter = 0;
if (log_counter++ % 30 == 0 || pointerCount > 1) {
__android_log_print(ANDROID_LOG_INFO, TAG,
"Touch: action=%d pointers=%d", action, pointerCount);
if (pointerCount > 1) {
__android_log_print(ANDROID_LOG_INFO, TAG,
" Finger 0: (%.0f, %.0f) Finger 1: (%.0f, %.0f)",
x_arr[0], y_arr[0], x_arr[1], y_arr[1]);
}
}

// Release arrays
(*env)->ReleaseFloatArrayElements(env, x, x_arr, JNI_ABORT);
(*env)->ReleaseFloatArrayElements(env, y, y_arr, JNI_ABORT);
(*env)->ReleaseLongArrayElements(env, pointerIds, id_arr, JNI_ABORT);
}
24 changes: 24 additions & 0 deletions app/src/main/jni/tuxpaint/src/android_multitouch.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
android_multitouch.h

Android-specific multitouch handling via JNI
*/

#ifndef ANDROID_MULTITOUCH_H
#define ANDROID_MULTITOUCH_H

#ifdef __ANDROID__

// Initialize multitouch system
void android_multitouch_init(void);

// Get current number of active pointers
int android_multitouch_get_count(void);

// Get pointer data by index (0-based)
// Returns 1 if pointer exists, 0 if not
int android_multitouch_get_pointer(int index, long *out_id, float *out_x, float *out_y, float *out_last_x, float *out_last_y);

#endif /* __ANDROID__ */

#endif /* ANDROID_MULTITOUCH_H */
Loading