Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3e05a0be authored by Gilles Debunne's avatar Gilles Debunne
Browse files

Hysteresis effect in Text selection.

Vertical movement requires going over a given threshold to change line.
Makes it easier to move down without changing line, so that one can see the
cursor better. Also simplifies long line selection.

Change-Id: I791da500232c6e510af64c637ed994c5da9a4fea
parent 0c76c7c5
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -223015,7 +223015,9 @@
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="offset" type="int">
<parameter name="x" type="int">
</parameter>
<parameter name="y" type="int">
</parameter>
</method>
<field name="FADE_OUT_DURATION"
+1 −2
Original line number Diff line number Diff line
@@ -287,8 +287,7 @@ public class ArrowKeyMovementMethod implements MovementMethod {
                    // Offset the current touch position (from controller to cursor)
                    final float x = event.getX() + mCursorController.getOffsetX();
                    final float y = event.getY() + mCursorController.getOffsetY();
                    int offset = widget.getOffset((int) x, (int) y);
                    mCursorController.updatePosition(offset);
                    mCursorController.updatePosition((int) x, (int) y);
                    return true;

                case MotionEvent.ACTION_UP:
+62 −37
Original line number Diff line number Diff line
@@ -7475,7 +7475,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        /**
         * Update the controller's position.
         */
        public void updatePosition(int offset);
        public void updatePosition(int x, int y);

        /**
         * The controller and the cursor's positions can be link by a fixed offset,
@@ -7510,7 +7510,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        // Vertical extension of the touch region
        int mTopExtension, mBottomExtension;
        // Position of the virtual finger position on screen
        int mHopSpotVertcalPosition;
        int mHotSpotVerticalPosition;

        Handle(Drawable drawable) {
            mDrawable = drawable;
@@ -7523,8 +7523,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
            final int lineTop = mLayout.getLineTop(line);
            final int lineBottom = mLayout.getLineBottom(line);

            mHopSpotVertcalPosition = lineTop + (bottom ? (3 * (lineBottom - lineTop)) / 4 :
                (lineBottom - lineTop) / 4);
            mHotSpotVerticalPosition = lineTop;

            final Rect bounds = sCursorControllerTempRect;
            bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - drawableWidth / 2.0);
@@ -7544,7 +7543,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener

            int boundTopBefore = bounds.top;
            convertFromViewportToContentCoordinates(bounds);
            mHopSpotVertcalPosition += bounds.top - boundTopBefore;
            mHotSpotVerticalPosition += bounds.top - boundTopBefore;
            mDrawable.setBounds(bounds);
            postInvalidate();
        }
@@ -7595,7 +7594,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener

        public void show() {
            updateDrawablePosition();
            // Has to be done after updatePosition, so that previous position invalidate
            // Has to be done after updateDrawablePosition, so that previous position invalidate
            // in only done if necessary.
            mIsVisible = true;
        }
@@ -7632,13 +7631,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
            }
        }

        public void updatePosition(int offset) {
            if (offset == getSelectionStart()) {
                return; // No change, no need to redraw
            }
        public void updatePosition(int x, int y) {
            final int previousOffset = getSelectionStart();
            int offset = getHysteresisOffset(x, y, previousOffset);

            if (offset != previousOffset) {
                Selection.setSelection((Spannable) mText, offset);
                updateDrawablePosition();
            }
        }

        private void updateDrawablePosition() {
            if (mIsVisible) {
@@ -7683,7 +7684,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener

                            final Rect bounds = mHandle.mDrawable.getBounds();
                            mOffsetX = (bounds.left + bounds.right) / 2.0f - x;
                            mOffsetY = mHandle.mHopSpotVertcalPosition - y;
                            mOffsetY = mHandle.mHotSpotVerticalPosition - y;

                            mOnDownTimerStart = event.getEventTime();
                        }
@@ -7738,7 +7739,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener

        public void show() {
            updateDrawablesPositions();
            // Has to be done after updatePosition, so that previous position invalidate
            // Has to be done after updateDrawablePositions, so that previous position invalidate
            // in only done if necessary.
            mIsVisible = true;
            mFadeOutTimerStart = -1;
@@ -7780,10 +7781,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
            }
        }

        public void updatePosition(int offset) {
        public void updatePosition(int x, int y) {
            int selectionStart = getSelectionStart();
            int selectionEnd = getSelectionEnd();

            final int previousOffset = mStartIsDragged ? selectionStart : selectionEnd;
            int offset = getHysteresisOffset(x, y, previousOffset);

            // Handle the case where start and end are swapped, making sure start <= end
            if (mStartIsDragged) {
                if (offset <= selectionEnd) {
@@ -7865,7 +7869,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                                    final Handle draggedHandle = mStartIsDragged ? mStartHandle : mEndHandle;
                                    final Rect bounds = draggedHandle.mDrawable.getBounds();
                                    mOffsetX = (bounds.left + bounds.right) / 2.0f - x;
                                    mOffsetY = draggedHandle.mHopSpotVertcalPosition - y;
                                    mOffsetY = draggedHandle.mHotSpotVerticalPosition - y;

                                    ((ArrowKeyMovementMethod)mMovement).setCursorController(this);
                                }
@@ -7935,6 +7939,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        stopTextSelectionMode();
    }

    private int getOffsetForHorizontal(int line, int x) {
        x -= getTotalPaddingLeft();
        // Clamp the position to inside of the view.
        x = Math.max(0, x);
        x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
        x += getScrollX();
        return getLayout().getOffsetForHorizontal(line, x);
    }

    /**
     * Get the offset character closest to the specified absolute position.
     *
@@ -7946,32 +7959,44 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
     * @hide
     */
    public int getOffset(int x, int y) {
        x -= getTotalPaddingLeft();
        y -= getTotalPaddingTop();
        if (getLayout() == null) return -1;

        y -= getTotalPaddingTop();
        // Clamp the position to inside of the view.
        if (x < 0) {
            x = 0;
        } else if (x >= (getWidth() - getTotalPaddingRight())) {
            x = getWidth()-getTotalPaddingRight() - 1;
        }
        if (y < 0) {
            y = 0;
        } else if (y >= (getHeight() - getTotalPaddingBottom())) {
            y = getHeight()-getTotalPaddingBottom() - 1;
        y = Math.max(0, y);
        y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
        y += getScrollY();

        final int line = getLayout().getLineForVertical(y);
        final int offset = getOffsetForHorizontal(line, x);
        return offset;
    }

        x += getScrollX();
    int getHysteresisOffset(int x, int y, int previousOffset) {
        final Layout layout = getLayout();
        if (layout == null) return -1;

        y -= getTotalPaddingTop();
        // Clamp the position to inside of the view.
        y = Math.max(0, y);
        y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
        y += getScrollY();

        Layout layout = getLayout();
        if (layout != null) {
            final int line = layout.getLineForVertical(y);
            final int offset = layout.getOffsetForHorizontal(line, x);
            return offset;
        } else {
            return -1;
        int line = getLayout().getLineForVertical(y);

        final int previousLine = layout.getLineForOffset(previousOffset);
        final int previousLineTop = layout.getLineTop(previousLine);
        final int previousLineBottom = layout.getLineBottom(previousLine);
        final int hysteresisThreshold = (previousLineBottom - previousLineTop) / 2;

        // If new line is just before or after previous line and y position is less than
        // hysteresisThreshold away from previous line, keep cursor on previous line.
        if (((line == previousLine + 1) && ((y - previousLineBottom) < hysteresisThreshold)) ||
            ((line == previousLine - 1) && ((previousLineTop - y)    < hysteresisThreshold))) {
            line = previousLine;
        }

        return getOffsetForHorizontal(line, x);
    }

    @ViewDebug.ExportedProperty