diff --git a/app/build.gradle b/app/build.gradle index caf964d..8580fa4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "xyz.whcself.whcshell" minSdk 26 targetSdk 35 - versionCode 1 - versionName "0.1.0" + versionCode 2 + versionName "0.1.1" } buildTypes { diff --git a/app/src/main/java/xyz/whcself/whcshell/TermView.java b/app/src/main/java/xyz/whcself/whcshell/TermView.java index 5500972..694c1d6 100644 --- a/app/src/main/java/xyz/whcself/whcshell/TermView.java +++ b/app/src/main/java/xyz/whcself/whcshell/TermView.java @@ -284,10 +284,15 @@ public final class TermView extends View { return Math.max(0, Math.min(cols - 1, c)); } + /** Top of the first text row; the renderer starts rows below the padding by this offset. */ + private int textTop() { + return getPaddingTop() + renderer.getFontLineSpacingAndAscent(); + } + /** Visible row, 0-based. */ private int row(MotionEvent ev) { int rows = emulator().mRows; - int r = (int) Math.floor((ev.getY() - getPaddingTop()) / renderer.getFontLineSpacing()); + int r = (int) Math.floor((ev.getY() - textTop()) / renderer.getFontLineSpacing()); return Math.max(0, Math.min(rows - 1, r)); } @@ -448,6 +453,49 @@ public final class TermView extends View { return m; } + private float gestureY, pinchDist; + + /** + * Touchpad gestures arrive as mouse-source events with a finger tool and no buttons: a fake + * finger classified as a two-finger swipe for scrolling (the gesture axes stay 0, so the finger's + * movement is the distance), two fake fingers for a pinch, and stray single-finger strokes around + * taps. None of them are button presses. + */ + private boolean onTouchpadGesture(MotionEvent ev) { + int action = ev.getActionMasked(); + if (ev.getPointerCount() >= 2) { + float d = (float) Math.hypot(ev.getX(0) - ev.getX(1), ev.getY(0) - ev.getY(1)); + if (action == MotionEvent.ACTION_POINTER_DOWN) { + pinchDist = d; + } else if (action == MotionEvent.ACTION_MOVE && pinchDist > 0 && actions != null) { + if (d > pinchDist * 1.25f) { + actions.zoom(1); + pinchDist = d; + } else if (d < pinchDist / 1.25f) { + actions.zoom(-1); + pinchDist = d; + } + } + return true; + } + pinchDist = 0; + boolean swipe = android.os.Build.VERSION.SDK_INT >= 34 + && ev.getClassification() == MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE; + if (action == MotionEvent.ACTION_DOWN) { + gestureY = ev.getY(); + scrollAccum = 0; + } else if (action == MotionEvent.ACTION_MOVE && swipe) { + scrollAccum += (gestureY - ev.getY()) / renderer.getFontLineSpacing(); + gestureY = ev.getY(); + int lines = (int) scrollAccum; + if (lines != 0) { + scrollAccum -= lines; + scrollLines(lines, ev); + } + } + return true; + } + private static final int[][] BUTTONS = { {MotionEvent.BUTTON_PRIMARY, TerminalEmulator.MOUSE_LEFT_BUTTON}, {MotionEvent.BUTTON_TERTIARY, 1}, @@ -459,11 +507,12 @@ public final class TermView extends View { * button, so presses and releases are derived from changes in the button state. */ private boolean onMouse(MotionEvent ev) { + if (ev.getToolType(0) == MotionEvent.TOOL_TYPE_FINGER) return onTouchpadGesture(ev); TerminalEmulator e = emulator(); int action = ev.getActionMasked(); int state = ev.getButtonState() & (MotionEvent.BUTTON_PRIMARY | MotionEvent.BUTTON_SECONDARY | MotionEvent.BUTTON_TERTIARY); - // Touchpad taps and injected events can come as DOWN/UP without any button state. + // Injected events (adb input mouse) come as DOWN/UP without any button state. if (action == MotionEvent.ACTION_DOWN && state == 0) implicitPrimary = true; if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) implicitPrimary = false; if (implicitPrimary) state |= MotionEvent.BUTTON_PRIMARY; @@ -524,8 +573,8 @@ public final class TermView extends View { } else if (selDragging && (state & MotionEvent.BUTTON_PRIMARY) != 0 && action == MotionEvent.ACTION_MOVE) { float y = ev.getY(); - if (y < getPaddingTop()) scrollTranscript(-1); - else if (y > getHeight() - getPaddingBottom()) scrollTranscript(1); + if (y < textTop()) scrollTranscript(-1); + else if (y >= textTop() + emulator().mRows * renderer.getFontLineSpacing()) scrollTranscript(1); extendSelection(col, row + topRow); } if ((released & MotionEvent.BUTTON_PRIMARY) != 0 && selDragging) {