From a99faf09e0a0d0280eeb19348c8f26a40d568e0a Mon Sep 17 00:00:00 2001 From: Sergey Avramenko Date: Fri, 8 May 2026 15:16:09 +0300 Subject: [PATCH] ANDROID: Add second screen touchpad for dual-display devices Add touchpad support on secondary PRESENTATION displays for dual-screen Android devices (e.g. AYN Thor). Touch on the second screen controls the mouse cursor in ScummVM. Gestures: single finger drag for mouse movement, single tap for left click, two-finger tap for right click, two-finger drag for scroll wheel. Uses existing pushEvent() types (JE_MOUSE_MOVE, JE_LMB_DOWN/UP, JE_RMB_DOWN/UP, JE_MOUSE_WHEEL_UP/DOWN) with no native C++ changes. Toggle button in on-screen overlay, enabled by default when secondary display is detected. OLED-friendly black background on touchpad screen. Tested on AYN Thor (Snapdragon 8 Gen 2, Android 14, dual display 1080x1920 + 1080x1240). Assisted-by: Claude:claude-opus-4.6 --- .../org/scummvm/scummvm/ScummVMActivity.java | 38 +++ .../scummvm/scummvm/SecondScreenHelper.java | 86 ++++++ .../scummvm/SecondScreenInitializer.java | 78 +++++ .../scummvm/scummvm/SecondScreenManager.java | 134 ++++++++ .../scummvm/SecondScreenPresentation.java | 51 ++++ .../scummvm/SecondScreenTouchpadView.java | 288 ++++++++++++++++++ dists/android/res/layout/scummvm_activity.xml | 13 +- 7 files changed, 687 insertions(+), 1 deletion(-) create mode 100644 backends/platform/android/org/scummvm/scummvm/SecondScreenHelper.java create mode 100644 backends/platform/android/org/scummvm/scummvm/SecondScreenInitializer.java create mode 100644 backends/platform/android/org/scummvm/scummvm/SecondScreenManager.java create mode 100644 backends/platform/android/org/scummvm/scummvm/SecondScreenPresentation.java create mode 100644 backends/platform/android/org/scummvm/scummvm/SecondScreenTouchpadView.java diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java index 78306e0eebd0..2aa8e920e418 100644 --- a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java +++ b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java @@ -130,6 +130,8 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis private GridLayout _buttonLayout = null; private ImageView _toggleTouchModeKeyboardBtnIcon = null; private ImageView _openMenuBtnIcon = null; + private ImageView _toggleSecondScreenBtnIcon = null; + private SecondScreenManager _secondScreenManager = null; private LedView _ioLed = null; private int _layoutOrientation; @@ -697,6 +699,22 @@ public void run() { } }; + public final View.OnClickListener secondScreenBtnOnClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + runOnUiThread(new Runnable() { + public void run() { + if (_secondScreenManager != null) { + boolean enabled = _secondScreenManager.toggle(); + if (_toggleSecondScreenBtnIcon != null) { + _toggleSecondScreenBtnIcon.setAlpha(enabled ? 1.0f : 0.4f); + } + } + } + }); + } + }; + private class MyScummVM extends ScummVM { public MyScummVM(SurfaceHolder holder, final MyScummVMDestroyedCallback destroyedCallback) { @@ -1113,6 +1131,21 @@ public void handle(int exitResult) { _toggleTouchModeKeyboardBtnIcon.setOnLongClickListener(touchModeKeyboardBtnOnLongClickListener); _openMenuBtnIcon.setOnClickListener(menuBtnOnClickListener); + // Second screen touchpad support (API 17+) + _toggleSecondScreenBtnIcon = findViewById(R.id.toggle_second_screen_button); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + _secondScreenManager = new SecondScreenManager(this, _scummvm); + _secondScreenManager.start(); + if (_secondScreenManager.hasSecondaryDisplay()) { + _toggleSecondScreenBtnIcon.setVisibility(View.VISIBLE); + _toggleSecondScreenBtnIcon.setOnClickListener(secondScreenBtnOnClickListener); + } else { + _toggleSecondScreenBtnIcon.setVisibility(View.GONE); + } + } else { + _toggleSecondScreenBtnIcon.setVisibility(View.GONE); + } + // Keyboard visibility listener - mainly to hide system UI if keyboard is shown and we return from Suspend to the Activity setKeyboardVisibilityListener(this); @@ -1260,6 +1293,11 @@ public void onDestroy() { hideScreenKeyboard(); } + if (_secondScreenManager != null) { + _secondScreenManager.stop(); + _secondScreenManager = null; + } + if (_events != null) { _finishing = true; diff --git a/backends/platform/android/org/scummvm/scummvm/SecondScreenHelper.java b/backends/platform/android/org/scummvm/scummvm/SecondScreenHelper.java new file mode 100644 index 000000000000..76317abc5cfd --- /dev/null +++ b/backends/platform/android/org/scummvm/scummvm/SecondScreenHelper.java @@ -0,0 +1,86 @@ +package org.scummvm.scummvm; + +import android.app.Activity; +import android.app.Application; +import android.os.Build; +import android.os.Bundle; +import android.util.DisplayMetrics; +import android.view.View; +import android.view.WindowManager; +import android.widget.ImageView; + +public class SecondScreenHelper { + + private static SecondScreenManager sManager; + private static ImageView sToggleBtn; + private static Application.ActivityLifecycleCallbacks sLifecycleCallbacks; + + public static void init(Activity activity, ScummVM scummvm) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) return; + + DisplayMetrics dm = new DisplayMetrics(); + WindowManager wm = (WindowManager) activity.getSystemService(Activity.WINDOW_SERVICE); + if (wm != null) { + wm.getDefaultDisplay().getRealMetrics(dm); + } + int screenW = Math.max(dm.widthPixels, dm.heightPixels); + int screenH = Math.min(dm.widthPixels, dm.heightPixels); + + sManager = new SecondScreenManager(activity, scummvm, screenW, screenH); + sManager.start(); + + int btnId = activity.getResources().getIdentifier( + "toggle_second_screen_button", "id", activity.getPackageName()); + if (btnId != 0) { + sToggleBtn = activity.findViewById(btnId); + } + + if (sToggleBtn != null && sManager.hasSecondaryDisplay()) { + sToggleBtn.setVisibility(View.VISIBLE); + sToggleBtn.setAlpha(1.0f); + sToggleBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (sManager != null) { + boolean enabled = sManager.toggle(); + if (sToggleBtn != null) { + sToggleBtn.setAlpha(enabled ? 1.0f : 0.4f); + } + } + } + }); + + sManager.toggle(); + + sLifecycleCallbacks = new Application.ActivityLifecycleCallbacks() { + @Override public void onActivityCreated(Activity a, Bundle s) {} + @Override public void onActivityStarted(Activity a) {} + @Override + public void onActivityResumed(Activity a) { + if (a instanceof ScummVMActivity && sManager != null) { + sManager.onResume(); + } + } + @Override + public void onActivityPaused(Activity a) { + if (a instanceof ScummVMActivity && sManager != null) { + sManager.onPause(); + } + } + @Override public void onActivityStopped(Activity a) {} + @Override public void onActivitySaveInstanceState(Activity a, Bundle o) {} + @Override public void onActivityDestroyed(Activity a) {} + }; + activity.getApplication().registerActivityLifecycleCallbacks(sLifecycleCallbacks); + } + } + + public static void destroy() { + if (sManager != null) { + sManager.stop(); + sManager = null; + } + sToggleBtn = null; + sLifecycleCallbacks = null; + } +} diff --git a/backends/platform/android/org/scummvm/scummvm/SecondScreenInitializer.java b/backends/platform/android/org/scummvm/scummvm/SecondScreenInitializer.java new file mode 100644 index 000000000000..fe3f6011a574 --- /dev/null +++ b/backends/platform/android/org/scummvm/scummvm/SecondScreenInitializer.java @@ -0,0 +1,78 @@ +package org.scummvm.scummvm; + +import android.app.Activity; +import android.app.Application; +import android.os.Build; +import android.os.Bundle; +import android.view.View; +import android.widget.ImageView; + +import androidx.annotation.RequiresApi; + +@RequiresApi(api = Build.VERSION_CODES.ICE_CREAM_SANDWICH) +public class SecondScreenInitializer implements Application.ActivityLifecycleCallbacks { + + private SecondScreenManager _manager; + private ImageView _toggleBtn; + + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + if (!(activity instanceof ScummVMActivity)) return; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) return; + + ScummVMActivity svm = (ScummVMActivity) activity; + + try { + java.lang.reflect.Field field = ScummVMActivity.class.getDeclaredField("_scummvm"); + field.setAccessible(true); + ScummVM scummvm = (ScummVM) field.get(svm); + if (scummvm == null) return; + + _manager = new SecondScreenManager(activity, scummvm); + _manager.start(); + + _toggleBtn = activity.findViewById( + activity.getResources().getIdentifier( + "toggle_second_screen_button", "id", activity.getPackageName())); + + if (_toggleBtn != null && _manager.hasSecondaryDisplay()) { + _toggleBtn.setVisibility(View.VISIBLE); + _toggleBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (_manager != null) { + boolean enabled = _manager.toggle(); + _toggleBtn.setAlpha(enabled ? 1.0f : 0.4f); + } + } + }); + } + } catch (Exception e) { + // _scummvm field not accessible or not yet initialized + } + } + + @Override + public void onActivityStarted(Activity activity) {} + + @Override + public void onActivityResumed(Activity activity) {} + + @Override + public void onActivityPaused(Activity activity) {} + + @Override + public void onActivityStopped(Activity activity) {} + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle outState) {} + + @Override + public void onActivityDestroyed(Activity activity) { + if (!(activity instanceof ScummVMActivity)) return; + if (_manager != null) { + _manager.stop(); + _manager = null; + } + } +} diff --git a/backends/platform/android/org/scummvm/scummvm/SecondScreenManager.java b/backends/platform/android/org/scummvm/scummvm/SecondScreenManager.java new file mode 100644 index 000000000000..e12b676bfc2f --- /dev/null +++ b/backends/platform/android/org/scummvm/scummvm/SecondScreenManager.java @@ -0,0 +1,134 @@ +package org.scummvm.scummvm; + +import android.content.Context; +import android.hardware.display.DisplayManager; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.view.Display; + +import androidx.annotation.RequiresApi; + +@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) +public class SecondScreenManager { + + private final Context _context; + private final ScummVM _scummvm; + private final DisplayManager _displayManager; + private final int _mainScreenWidth; + private final int _mainScreenHeight; + private SecondScreenPresentation _presentation; + private DisplayManager.DisplayListener _displayListener; + private boolean _enabled = false; + + public SecondScreenManager(Context context, ScummVM scummvm, int mainScreenWidth, int mainScreenHeight) { + _context = context; + _scummvm = scummvm; + _displayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); + _mainScreenWidth = mainScreenWidth; + _mainScreenHeight = mainScreenHeight; + } + + public void start() { + if (_displayManager == null) return; + + _displayListener = new DisplayManager.DisplayListener() { + @Override + public void onDisplayAdded(int displayId) { + if (_enabled) { + tryShowPresentation(); + } + } + + @Override + public void onDisplayRemoved(int displayId) { + dismissPresentation(); + } + + @Override + public void onDisplayChanged(int displayId) { + } + }; + + _displayManager.registerDisplayListener(_displayListener, + new Handler(Looper.getMainLooper())); + } + + public void stop() { + dismissPresentation(); + if (_displayManager != null && _displayListener != null) { + _displayManager.unregisterDisplayListener(_displayListener); + _displayListener = null; + } + } + + public boolean toggle() { + _enabled = !_enabled; + if (_enabled) { + tryShowPresentation(); + } else { + dismissPresentation(); + } + return _enabled; + } + + public boolean isEnabled() { + return _enabled; + } + + public boolean hasSecondaryDisplay() { + return findPresentationDisplay() != null; + } + + public void onPause() { + dismissPresentation(); + } + + public void onResume() { + if (_enabled) { + tryShowPresentation(); + } + } + + private void tryShowPresentation() { + Display display = findPresentationDisplay(); + if (display != null) { + showPresentation(display); + } + } + + private void showPresentation(Display display) { + dismissPresentation(); + try { + _presentation = new SecondScreenPresentation( + _context, display, _scummvm, _mainScreenWidth, _mainScreenHeight); + _presentation.show(); + } catch (Exception e) { + _presentation = null; + } + } + + private void dismissPresentation() { + if (_presentation != null) { + try { + _presentation.dismiss(); + } catch (Exception ignored) { + } + _presentation = null; + } + } + + private Display findPresentationDisplay() { + if (_displayManager == null) return null; + + Display[] displays = _displayManager.getDisplays( + DisplayManager.DISPLAY_CATEGORY_PRESENTATION); + + for (Display d : displays) { + if (d.getDisplayId() != 0 && !"HiddenDisplay".equals(d.getName())) { + return d; + } + } + return null; + } +} diff --git a/backends/platform/android/org/scummvm/scummvm/SecondScreenPresentation.java b/backends/platform/android/org/scummvm/scummvm/SecondScreenPresentation.java new file mode 100644 index 000000000000..ff0add22724f --- /dev/null +++ b/backends/platform/android/org/scummvm/scummvm/SecondScreenPresentation.java @@ -0,0 +1,51 @@ +package org.scummvm.scummvm; + +import android.app.Presentation; +import android.content.Context; +import android.os.Build; +import android.os.Bundle; +import android.view.Display; +import android.view.Window; +import android.view.WindowManager; + +import androidx.annotation.RequiresApi; + +@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) +public class SecondScreenPresentation extends Presentation { + + private final ScummVM _scummvm; + private final int _mainScreenWidth; + private final int _mainScreenHeight; + private SecondScreenTouchpadView _touchpadView; + + public SecondScreenPresentation(Context context, Display display, ScummVM scummvm, + int mainScreenWidth, int mainScreenHeight) { + super(context, display); + _scummvm = scummvm; + _mainScreenWidth = mainScreenWidth; + _mainScreenHeight = mainScreenHeight; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Window window = getWindow(); + if (window != null) { + window.setFlags( + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + ); + } + + _touchpadView = new SecondScreenTouchpadView(getContext(), _scummvm, + _mainScreenWidth, _mainScreenHeight); + setContentView(_touchpadView); + } + + public SecondScreenTouchpadView getTouchpadView() { + return _touchpadView; + } +} diff --git a/backends/platform/android/org/scummvm/scummvm/SecondScreenTouchpadView.java b/backends/platform/android/org/scummvm/scummvm/SecondScreenTouchpadView.java new file mode 100644 index 000000000000..4d45a772c6e9 --- /dev/null +++ b/backends/platform/android/org/scummvm/scummvm/SecondScreenTouchpadView.java @@ -0,0 +1,288 @@ +package org.scummvm.scummvm; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.os.Build; +import android.view.MotionEvent; +import android.view.View; + +import androidx.annotation.RequiresApi; + +@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) +public class SecondScreenTouchpadView extends View { + + private static final int JE_LMB_DOWN = 9; + private static final int JE_LMB_UP = 10; + private static final int JE_RMB_DOWN = 11; + private static final int JE_RMB_UP = 12; + private static final int JE_MOUSE_MOVE = 13; + private static final int JE_MOUSE_WHEEL_UP = 22; + private static final int JE_MOUSE_WHEEL_DOWN = 23; + + private static final int TAP_TIMEOUT_MS = 300; + private static final float TAP_SLOP_PX = 20f; + private static final float ACCELERATION_THRESHOLD = 6f; + private static final float ACCELERATION_MULTIPLIER = 1.5f; + private static final float SCROLL_THRESHOLD = 30f; + private static final float DEFAULT_SENSITIVITY = 1.5f; + + private final ScummVM _scummvm; + private final Paint _bgPaint; + private final Paint _borderPaint; + private final Paint _textPaint; + private final Paint _touchPaint; + private final Paint _touchPaintActive; + + private int _cursorX; + private int _cursorY; + private int _screenWidth; + private int _screenHeight; + + private float _lastTouchX; + private float _lastTouchY; + private boolean _isDragging; + private int _activePointers; + + private long _touchDownTime; + private float _touchDownX; + private float _touchDownY; + + private float _scrollBaseY; + private boolean _isScrolling; + private float _scrollAccum; + + private float _sensitivity = DEFAULT_SENSITIVITY; + private boolean _enabled = true; + + private float _currentTouchX = -1; + private float _currentTouchY = -1; + + public SecondScreenTouchpadView(Context context, ScummVM scummvm, + int mainScreenWidth, int mainScreenHeight) { + super(context); + _scummvm = scummvm; + _screenWidth = mainScreenWidth; + _screenHeight = mainScreenHeight; + _cursorX = _screenWidth / 2; + _cursorY = _screenHeight / 2; + + _bgPaint = new Paint(); + _bgPaint.setColor(0xFF000000); + _bgPaint.setStyle(Paint.Style.FILL); + + _borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + _borderPaint.setColor(0x20FFFFFF); + _borderPaint.setStyle(Paint.Style.STROKE); + _borderPaint.setStrokeWidth(1f); + + _textPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + _textPaint.setColor(0x30FFFFFF); + _textPaint.setTextSize(28f); + _textPaint.setTextAlign(Paint.Align.CENTER); + + _touchPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + _touchPaint.setColor(0x18FFFFFF); + _touchPaint.setStyle(Paint.Style.FILL); + + _touchPaintActive = new Paint(Paint.ANTI_ALIAS_FLAG); + _touchPaintActive.setColor(0x30FFFFFF); + _touchPaintActive.setStyle(Paint.Style.FILL); + } + + public void setScreenResolution(int w, int h) { + _screenWidth = Math.max(w, 1); + _screenHeight = Math.max(h, 1); + _cursorX = clamp(_cursorX, 0, _screenWidth - 1); + _cursorY = clamp(_cursorY, 0, _screenHeight - 1); + } + + public void setSensitivity(float sensitivity) { + _sensitivity = sensitivity; + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + int w = getWidth(); + int h = getHeight(); + + canvas.drawRect(0, 0, w, h, _bgPaint); + + float pad = 16f; + canvas.drawRoundRect(pad, pad, w - pad, h - pad, 16, 16, _borderPaint); + + if (_currentTouchX >= 0 && _currentTouchY >= 0) { + canvas.drawCircle(_currentTouchX, _currentTouchY, 40f, + _isDragging ? _touchPaintActive : _touchPaint); + } + + canvas.drawText("ScummVM Touchpad", w / 2f, h / 2f - 20, _textPaint); + + Paint hintPaint = new Paint(_textPaint); + hintPaint.setTextSize(20f); + hintPaint.setColor(0x40FFFFFF); + canvas.drawText("tap = click • 2-finger tap = right click • 2-finger drag = scroll", + w / 2f, h / 2f + 20, hintPaint); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (!_enabled) return false; + + int action = event.getActionMasked(); + + switch (action) { + case MotionEvent.ACTION_DOWN: + _activePointers = 1; + _touchDownX = event.getX(); + _touchDownY = event.getY(); + _touchDownTime = System.currentTimeMillis(); + _lastTouchX = event.getX(); + _lastTouchY = event.getY(); + _isDragging = false; + _isScrolling = false; + _scrollAccum = 0; + _currentTouchX = event.getX(); + _currentTouchY = event.getY(); + invalidate(); + return true; + + case MotionEvent.ACTION_POINTER_DOWN: + _activePointers = event.getPointerCount(); + if (_activePointers == 2) { + _scrollBaseY = averageY(event); + _isScrolling = false; + _scrollAccum = 0; + } + return true; + + case MotionEvent.ACTION_MOVE: + if (_activePointers == 1) { + float dx = event.getX() - _lastTouchX; + float dy = event.getY() - _lastTouchY; + _lastTouchX = event.getX(); + _lastTouchY = event.getY(); + _currentTouchX = event.getX(); + _currentTouchY = event.getY(); + + float dist = distance(_touchDownX, _touchDownY, event.getX(), event.getY()); + if (dist > TAP_SLOP_PX) { + _isDragging = true; + } + if (_isDragging) { + moveCursor(dx, dy); + } + invalidate(); + } else if (_activePointers == 2) { + float currentAvgY = averageY(event); + float scrollDelta = currentAvgY - _scrollBaseY; + _scrollAccum += scrollDelta; + _scrollBaseY = currentAvgY; + + if (Math.abs(_scrollAccum) > SCROLL_THRESHOLD) { + _isScrolling = true; + if (_scrollAccum < 0) { + _scummvm.pushEvent(JE_MOUSE_WHEEL_UP, _cursorX, _cursorY, 0, 0, 0, 0); + } else { + _scummvm.pushEvent(JE_MOUSE_WHEEL_DOWN, _cursorX, _cursorY, 0, 0, 0, 0); + } + _scrollAccum = 0; + } + } + return true; + + case MotionEvent.ACTION_POINTER_UP: + int newCount = event.getPointerCount() - 1; + if (_activePointers == 2 && newCount == 1 && !_isScrolling) { + long elapsed = System.currentTimeMillis() - _touchDownTime; + if (elapsed < TAP_TIMEOUT_MS) { + rightClick(); + } + } + _activePointers = newCount; + if (_activePointers == 1) { + int remaining = findRemainingPointer(event); + if (remaining >= 0) { + _lastTouchX = event.getX(remaining); + _lastTouchY = event.getY(remaining); + } + } + return true; + + case MotionEvent.ACTION_UP: + if (_activePointers == 1 && !_isDragging) { + long elapsed = System.currentTimeMillis() - _touchDownTime; + if (elapsed < TAP_TIMEOUT_MS) { + leftClick(); + } + } + _activePointers = 0; + _currentTouchX = -1; + _currentTouchY = -1; + invalidate(); + return true; + + case MotionEvent.ACTION_CANCEL: + _activePointers = 0; + _currentTouchX = -1; + _currentTouchY = -1; + invalidate(); + return true; + } + return false; + } + + private void moveCursor(float rawDx, float rawDy) { + float dx = rawDx * _sensitivity; + float dy = rawDy * _sensitivity; + + float mag = (float) Math.sqrt(dx * dx + dy * dy); + if (mag > ACCELERATION_THRESHOLD) { + dx *= ACCELERATION_MULTIPLIER; + dy *= ACCELERATION_MULTIPLIER; + } + + _cursorX = clamp(_cursorX + Math.round(dx), 0, _screenWidth - 1); + _cursorY = clamp(_cursorY + Math.round(dy), 0, _screenHeight - 1); + + _scummvm.pushEvent(JE_MOUSE_MOVE, _cursorX, _cursorY, 0, 0, 0, 0); + } + + private void leftClick() { + _scummvm.pushEvent(JE_LMB_DOWN, _cursorX, _cursorY, 0, 0, 0, 0); + _scummvm.pushEvent(JE_LMB_UP, _cursorX, _cursorY, 0, 0, 0, 0); + } + + private void rightClick() { + _scummvm.pushEvent(JE_RMB_DOWN, _cursorX, _cursorY, 0, 0, 0, 0); + _scummvm.pushEvent(JE_RMB_UP, _cursorX, _cursorY, 0, 0, 0, 0); + } + + private float averageY(MotionEvent event) { + float sum = 0; + int count = Math.min(event.getPointerCount(), 2); + for (int i = 0; i < count; i++) { + sum += event.getY(i); + } + return sum / count; + } + + private int findRemainingPointer(MotionEvent event) { + int actionIndex = event.getActionIndex(); + for (int i = 0; i < event.getPointerCount(); i++) { + if (i != actionIndex) return i; + } + return -1; + } + + private static float distance(float x1, float y1, float x2, float y2) { + float dx = x2 - x1; + float dy = y2 - y1; + return (float) Math.sqrt(dx * dx + dy * dy); + } + + private static int clamp(int val, int min, int max) { + return Math.max(min, Math.min(max, val)); + } +} diff --git a/dists/android/res/layout/scummvm_activity.xml b/dists/android/res/layout/scummvm_activity.xml index 7f1ed9050531..5395548655cb 100644 --- a/dists/android/res/layout/scummvm_activity.xml +++ b/dists/android/res/layout/scummvm_activity.xml @@ -44,13 +44,24 @@ android:visibility="gone" tools:visibility="visible" /> + +