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

Commit 42390aab authored by Mady Mellor's avatar Mady Mellor
Browse files

Allow text selection handles to scroll horizontally

When selecting text and expanding the selection, the handles snap
to the end (or start) of words. The handles don't snap until the
user has moved halfway through the word.

In horizontally scrolling text views, where some of the text is
cut off, the user cannot be halfway through the word, this causes
the selection to get stuck unless the user scrolls the view and then
continues selecting.

This CL does two things:

1) Checks if the user is close to the edge of the view when the
view can scroll horizontally, and places the cursor at next offset
if available.

2) Moves the code to check if handles are crossing into own method
this should be done each time the cursor is placed and avoids the
need to duplicate the check throughout updatePosition code.

Bug: 22657879
Change-Id: Ic14cb0994cd202a897bf6532f3832bb93ed49bfb
parent e95af4ab
Loading
Loading
Loading
Loading
+102 −18
Original line number Diff line number Diff line
@@ -4059,9 +4059,17 @@ public class Editor {
        private float mPrevX;
        // Indicates if the handle has moved a boundary between LTR and RTL text.
        private boolean mLanguageDirectionChanged = false;
        // Distance from edge of horizontally scrolling text view
        // to use to switch to character mode.
        private final float mTextViewEdgeSlop;
        // Used to save text view location.
        private final int[] mTextViewLocation = new int[2];

        public SelectionStartHandleView(Drawable drawableLtr, Drawable drawableRtl) {
            super(drawableLtr, drawableRtl);
            ViewConfiguration viewConfiguration = ViewConfiguration.get(
                    mTextView.getContext());
            mTextViewEdgeSlop = viewConfiguration.getScaledTouchSlop() * 4;
        }

        @Override
@@ -4099,7 +4107,7 @@ public class Editor {
            if (layout == null) {
                // HandleView will deal appropriately in positionAtCursorOffset when
                // layout is null.
                positionAtCursorOffset(mTextView.getOffsetForPosition(x, y), false);
                positionAndAdjustForCrossingHandles(mTextView.getOffsetForPosition(x, y));
                return;
            }

@@ -4141,12 +4149,12 @@ public class Editor {
                // to the current position.
                mLanguageDirectionChanged = true;
                mTouchWordDelta = 0.0f;
                positionAtCursorOffset(offset, false);
                positionAndAdjustForCrossingHandles(offset);
                return;
            } else if (mLanguageDirectionChanged && !isLvlBoundary) {
                // We've just moved past the boundary so update the position. After this we can
                // figure out if the user is expanding or shrinking to go by word or character.
                positionAtCursorOffset(offset, false);
                positionAndAdjustForCrossingHandles(offset);
                mTouchWordDelta = 0.0f;
                mLanguageDirectionChanged = false;
                return;
@@ -4159,6 +4167,21 @@ public class Editor {
                }
            }

            if (mTextView.getHorizontallyScrolling()) {
                if (positionNearEdgeOfScrollingView(x, atRtl)
                        && (mTextView.getScrollX() != 0)
                        && ((isExpanding && offset < selectionStart) || !isExpanding)) {
                    // If we're expanding ensure that the offset is smaller than the
                    // selection start, if the handle snapped to the word, the finger position
                    // may be out of sync and we don't want the selection to jump back.
                    mTouchWordDelta = 0.0f;
                    final int nextOffset = atRtl ? layout.getOffsetToRightOf(mPreviousOffset)
                            : layout.getOffsetToLeftOf(mPreviousOffset);
                    positionAndAdjustForCrossingHandles(nextOffset);
                    return;
                }
            }

            if (isExpanding) {
                // User is increasing the selection.
                if (!mInWord || currLine < mPrevLine) {
@@ -4214,16 +4237,21 @@ public class Editor {
            }

            if (positionCursor) {
                // Handles can not cross and selection is at least one character.
                mPreviousLineTouched = currLine;
                positionAndAdjustForCrossingHandles(offset);
            }
            mPrevX = x;
        }

        private void positionAndAdjustForCrossingHandles(int offset) {
            final int selectionEnd = mTextView.getSelectionEnd();
            if (offset >= selectionEnd) {
                // Handles can not cross and selection is at least one character.
                offset = getNextCursorOffset(selectionEnd, false);
                mTouchWordDelta = 0.0f;
            }
                mPreviousLineTouched = currLine;
            positionAtCursorOffset(offset, false);
        }
            mPrevX = x;
        }

        @Override
        protected void positionAtCursorOffset(int offset, boolean parentScrolled) {
@@ -4242,6 +4270,20 @@ public class Editor {
            }
            return superResult;
        }

        private boolean positionNearEdgeOfScrollingView(float x, boolean atRtl) {
            mTextView.getLocationOnScreen(mTextViewLocation);
            boolean nearEdge;
            if (atRtl) {
                int rightEdge = mTextViewLocation[0] + mTextView.getWidth()
                        - mTextView.getPaddingRight();
                nearEdge = x > rightEdge - mTextViewEdgeSlop;
            } else {
                int leftEdge = mTextViewLocation[0] + mTextView.getPaddingLeft();
                nearEdge = x < leftEdge + mTextViewEdgeSlop;
            }
            return nearEdge;
        }
    }

    private class SelectionEndHandleView extends HandleView {
@@ -4253,9 +4295,17 @@ public class Editor {
        private float mPrevX;
        // Indicates if the handle has moved a boundary between LTR and RTL text.
        private boolean mLanguageDirectionChanged = false;
        // Distance from edge of horizontally scrolling text view
        // to use to switch to character mode.
        private final float mTextViewEdgeSlop;
        // Used to save the text view location.
        private final int[] mTextViewLocation = new int[2];

        public SelectionEndHandleView(Drawable drawableLtr, Drawable drawableRtl) {
            super(drawableLtr, drawableRtl);
            ViewConfiguration viewConfiguration = ViewConfiguration.get(
                    mTextView.getContext());
            mTextViewEdgeSlop = viewConfiguration.getScaledTouchSlop() * 4;
        }

        @Override
@@ -4293,7 +4343,7 @@ public class Editor {
            if (layout == null) {
                // HandleView will deal appropriately in positionAtCursorOffset when
                // layout is null.
                positionAtCursorOffset(mTextView.getOffsetForPosition(x, y), false);
                positionAndAdjustForCrossingHandles(mTextView.getOffsetForPosition(x, y));
                return;
            }

@@ -4335,12 +4385,12 @@ public class Editor {
                // to the current position.
                mLanguageDirectionChanged = true;
                mTouchWordDelta = 0.0f;
                positionAtCursorOffset(offset, false);
                positionAndAdjustForCrossingHandles(offset);
                return;
            } else if (mLanguageDirectionChanged && !isLvlBoundary) {
                // We've just moved past the boundary so update the position. After this we can
                // figure out if the user is expanding or shrinking to go by word or character.
                positionAtCursorOffset(offset, false);
                positionAndAdjustForCrossingHandles(offset);
                mTouchWordDelta = 0.0f;
                mLanguageDirectionChanged = false;
                return;
@@ -4353,6 +4403,21 @@ public class Editor {
                }
            }

            if (mTextView.getHorizontallyScrolling()) {
                if (positionNearEdgeOfScrollingView(x, atRtl)
                        && mTextView.canScrollHorizontally(atRtl ? -1 : 1)
                        && ((isExpanding && offset > selectionEnd) || !isExpanding)) {
                    // If we're expanding ensure that the offset is actually greater than the
                    // selection end, if the handle snapped to the word, the finger position
                    // may be out of sync and we don't want the selection to jump back.
                    mTouchWordDelta = 0.0f;
                    final int nextOffset = atRtl ? layout.getOffsetToLeftOf(mPreviousOffset)
                            : layout.getOffsetToRightOf(mPreviousOffset);
                    positionAndAdjustForCrossingHandles(nextOffset);
                    return;
                }
            }

            if (isExpanding) {
                // User is increasing the selection.
                if (!mInWord || currLine > mPrevLine) {
@@ -4408,16 +4473,21 @@ public class Editor {
            }

            if (positionCursor) {
                // Handles can not cross and selection is at least one character.
                mPreviousLineTouched = currLine;
                positionAndAdjustForCrossingHandles(offset);
            }
            mPrevX = x;
        }

        private void positionAndAdjustForCrossingHandles(int offset) {
            final int selectionStart = mTextView.getSelectionStart();
            if (offset <= selectionStart) {
                // Handles can not cross and selection is at least one character.
                offset = getNextCursorOffset(selectionStart, true);
                mTouchWordDelta = 0.0f;
            }
                mPreviousLineTouched = currLine;
            positionAtCursorOffset(offset, false);
        }
            mPrevX = x;
        }

        @Override
        protected void positionAtCursorOffset(int offset, boolean parentScrolled) {
@@ -4436,6 +4506,20 @@ public class Editor {
            }
            return superResult;
        }

        private boolean positionNearEdgeOfScrollingView(float x, boolean atRtl) {
            mTextView.getLocationOnScreen(mTextViewLocation);
            boolean nearEdge;
            if (atRtl) {
                int leftEdge = mTextViewLocation[0] + mTextView.getPaddingLeft();
                nearEdge = x < leftEdge + mTextViewEdgeSlop;
            } else {
                int rightEdge = mTextViewLocation[0] + mTextView.getWidth()
                        - mTextView.getPaddingRight();
                nearEdge = x > rightEdge - mTextViewEdgeSlop;
            }
            return nearEdge;
        }
    }

    private int getCurrentLineAdjustedForSlop(Layout layout, int prevLine, float y) {