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

Commit f75c97e0 authored by Gilles Debunne's avatar Gilles Debunne
Browse files

Text insertion cursor is now defined by a Drawable.

Bug 3261766

If defined, the drawable is used instead of directly drawing a 1 pixel
line. This makes the cursor more fancy and more visible.

The drawable is currently clipped by the TextView's limits, which is
currently visible on the left when the cursor is at the first position.
To solve this issue properly, we would need to propagate a do-not-clip
up in the hierarchy.

Change-Id: I99f6001048eed14104994acf6bab942dda8eb38e
parent 302b9884
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -600,8 +600,9 @@ public abstract class Layout {
     * are at different run levels (and thus there's a split caret).
     * @param offset the offset
     * @return true if at a level boundary
     * @hide
     */
    private boolean isLevelBoundary(int offset) {
    public boolean isLevelBoundary(int offset) {
        int line = getLineForOffset(offset);
        Directions dirs = getLineDirections(line);
        if (dirs == DIRS_ALL_LEFT_TO_RIGHT || dirs == DIRS_ALL_RIGHT_TO_LEFT) {
@@ -1148,8 +1149,7 @@ public abstract class Layout {
        int bottom = getLineTop(line+1);

        float h1 = getPrimaryHorizontal(point) - 0.5f;
        float h2 = isLevelBoundary(point) ?
                    getSecondaryHorizontal(point) - 0.5f : h1;
        float h2 = isLevelBoundary(point) ? getSecondaryHorizontal(point) - 0.5f : h1;

        int caps = TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_SHIFT_ON) |
                   TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_SELECTING);
+122 −43
Original line number Diff line number Diff line
@@ -304,15 +304,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
    }
    InputMethodState mInputMethodState;

    int mTextSelectHandleLeftRes;
    int mTextSelectHandleRightRes;
    int mTextSelectHandleRes;
    int mTextEditPasteWindowLayout, mTextEditSidePasteWindowLayout;
    int mTextEditNoPasteWindowLayout, mTextEditSideNoPasteWindowLayout;
    private int mTextSelectHandleLeftRes;
    private int mTextSelectHandleRightRes;
    private int mTextSelectHandleRes;
    private int mTextEditPasteWindowLayout, mTextEditSidePasteWindowLayout;
    private int mTextEditNoPasteWindowLayout, mTextEditSideNoPasteWindowLayout;

    Drawable mSelectHandleLeft;
    Drawable mSelectHandleRight;
    Drawable mSelectHandleCenter;
    private int mCursorDrawableRes;
    private final Drawable[] mCursorDrawable = new Drawable[2];
    private int mCursorCount; // Actual current number of used mCursorDrawable: 0, 1 or 2

    private Drawable mSelectHandleLeft;
    private Drawable mSelectHandleRight;
    private Drawable mSelectHandleCenter;

    private int mLastDownPositionX, mLastDownPositionY;
    private Callback mCustomSelectionActionModeCallback;
@@ -742,6 +746,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                }
                break;

            case com.android.internal.R.styleable.TextView_textCursorDrawable:
                mCursorDrawableRes = a.getResourceId(attr, 0);
                break;

            case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
                mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
                break;
@@ -3770,6 +3778,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        if (mHighlightPathBogus) {
            invalidateCursor();
        } else {
            final int horizontalPadding = getCompoundPaddingLeft();
            final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);

            if (mCursorCount == 0) {
                synchronized (sTempRect) {
                    /*
                     * The reason for this concern about the thickness of the
@@ -3780,23 +3792,26 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                     * make sure the entire cursor gets invalidated instead of
                     * sometimes missing half a pixel.
                     */

                    float thick = FloatMath.ceil(mTextPaint.getStrokeWidth());
                    if (thick < 1.0f) {
                        thick = 1.0f;
                    }

                thick /= 2;
                    thick /= 2.0f;

                    mHighlightPath.computeBounds(sTempRect, false);

                int left = getCompoundPaddingLeft();
                int top = getExtendedPaddingTop() + getVerticalOffset(true);

                invalidate((int) FloatMath.floor(left + sTempRect.left - thick),
                           (int) FloatMath.floor(top + sTempRect.top - thick),
                           (int) FloatMath.ceil(left + sTempRect.right + thick),
                           (int) FloatMath.ceil(top + sTempRect.bottom + thick));
                    invalidate((int) FloatMath.floor(horizontalPadding + sTempRect.left - thick),
                            (int) FloatMath.floor(verticalPadding + sTempRect.top - thick),
                            (int) FloatMath.ceil(horizontalPadding + sTempRect.right + thick),
                            (int) FloatMath.ceil(verticalPadding + sTempRect.bottom + thick));
                }
            } else {
                for (int i = 0; i < mCursorCount; i++) {
                    Rect bounds = mCursorDrawable[i].getBounds();
                    invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
                            bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
                }
            }
        }
    }
@@ -3836,13 +3851,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                    line2 = mLayout.getLineForOffset(last);

                int bottom = mLayout.getLineTop(line2 + 1);
                int voffset = getVerticalOffset(true);

                int left = getCompoundPaddingLeft() + mScrollX;
                invalidate(left, top + voffset + getExtendedPaddingTop(),
                           left + getWidth() - getCompoundPaddingLeft() -
                           getCompoundPaddingRight(),
                           bottom + voffset + getExtendedPaddingTop());
                final int horizontalPadding = getCompoundPaddingLeft();
                final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
                
                // If used, the cursor drawables can have an arbitrary dimension that can go beyond
                // the invalidated lines specified above.
                for (int i = 0; i < mCursorCount; i++) {
                    Rect bounds = mCursorDrawable[i].getBounds();
                    top = Math.min(top, bounds.top);
                    bottom = Math.max(bottom, bounds.bottom);
                    // Horizontal bounds are already full width, no need to update
                }

                invalidate(horizontalPadding + mScrollX, top + verticalPadding,
                        horizontalPadding + mScrollX + getWidth() -
                        getCompoundPaddingLeft() - getCompoundPaddingRight(),
                        bottom + verticalPadding);
            }
        }
    }
@@ -4346,6 +4371,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener

        Path highlight = null;
        int selStart = -1, selEnd = -1;
        boolean drawCursor = false;

        //  If there is no movement method, then there can be no selection.
        //  Check that first and attempt to skip everything having to do with
@@ -4366,6 +4392,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                        if (mHighlightPathBogus) {
                            mHighlightPath.reset();
                            mLayout.getCursorPath(selStart, mHighlightPath, mText);
                            updateCursorsPositions();
                            mHighlightPathBogus = false;
                        }

@@ -4377,8 +4404,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                        }
                        mHighlightPaint.setStyle(Paint.Style.STROKE);

                        if (mCursorCount > 0) {
                            drawCursor = true;
                        } else {
                            highlight = mHighlightPath;
                        }
                    }
                } else {
                    if (mHighlightPathBogus) {
                        mHighlightPath.reset();
@@ -4460,6 +4491,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
            mCorrectionHighlighter.draw(canvas, cursorOffsetVertical);
        }

        if (drawCursor) drawCursor(canvas, cursorOffsetVertical);

        layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);

        if (mMarquee != null && mMarquee.shouldDrawGhost()) {
@@ -4478,6 +4511,52 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        updateCursorControllerPositions();
    }

    private void updateCursorsPositions() {
        if (mCursorDrawableRes == 0) return;

        final int offset = getSelectionStart();
        final int line = mLayout.getLineForOffset(offset);
        final int top = mLayout.getLineTop(line);
        final int bottom = mLayout.getLineTop(line + 1);

        mCursorCount = mLayout.isLevelBoundary(offset) ? 2 : 1;

        int middle = bottom;
        if (mCursorCount == 2) {
            // Similar to what is done in {@link Layout.#getCursorPath(int, Path, CharSequence)}
            middle = (top + bottom) >> 1;
        }

        updateCursorPosition(0, top, middle, mLayout.getPrimaryHorizontal(offset));

        if (mCursorCount == 2) {
            updateCursorPosition(1, middle, bottom, mLayout.getSecondaryHorizontal(offset));
        }
    }

    private void updateCursorPosition(int cursorIndex, int top, int bottom, float horizontal) {
        if (mCursorDrawable[cursorIndex] == null)
            mCursorDrawable[cursorIndex] = mContext.getResources().getDrawable(mCursorDrawableRes);

        if (mTempRect == null) mTempRect = new Rect();

        mCursorDrawable[cursorIndex].getPadding(mTempRect);
        final int width = mCursorDrawable[cursorIndex].getIntrinsicWidth();
        horizontal = Math.max(0.5f, horizontal - 0.5f);
        final int left = (int) (horizontal) - mTempRect.left;
        mCursorDrawable[cursorIndex].setBounds(left, top - mTempRect.top, left + width,
                bottom + mTempRect.bottom);
    }

    private void drawCursor(Canvas canvas, int cursorOffsetVertical) {
        final boolean translate = cursorOffsetVertical != 0;
        if (translate) canvas.translate(0, cursorOffsetVertical);
        for (int i = 0; i < mCursorCount; i++) {
            mCursorDrawable[i].draw(canvas);
        }
        if (translate) canvas.translate(0, -cursorOffsetVertical);
    }

    /**
     * Update the positions of the CursorControllers.  Needed by WebTextView,
     * which does not draw.
@@ -8699,7 +8778,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                }
                mDrawable = mSelectHandleLeft;
                handleWidth = mDrawable.getIntrinsicWidth();
                mHotspotX = (handleWidth * 3) / 4;
                mHotspotX = handleWidth * 3.0f / 4.0f;
                break;
            }

@@ -8710,7 +8789,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                }
                mDrawable = mSelectHandleRight;
                handleWidth = mDrawable.getIntrinsicWidth();
                mHotspotX = handleWidth / 4;
                mHotspotX = handleWidth / 4.0f;
                break;
            }

@@ -8722,7 +8801,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                }
                mDrawable = mSelectHandleCenter;
                handleWidth = mDrawable.getIntrinsicWidth();
                mHotspotX = handleWidth / 2;
                mHotspotX = handleWidth / 2.0f;
                mIsInsertionHandle = true;
                break;
            }
@@ -8937,8 +9016,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
            final int lineBottom = mLayout.getLineBottom(line);

            final Rect bounds = sCursorControllerTempRect;
            bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - mHotspotX)
                + TextView.this.mScrollX;
            bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX) +
                    TextView.this.mScrollX;
            bounds.top = (bottom ? lineBottom : lineTop - mHeight) + TextView.this.mScrollY;

            bounds.right = bounds.left + width;
+274 B
Loading image diff...
+249 B
Loading image diff...
+6 −0
Original line number Diff line number Diff line
@@ -778,6 +778,9 @@
    <!-- Color of link text (URLs). -->
    <attr name="textColorLink" format="reference|color" />

    <!-- Reference to a drawable that will be drawn under the insertion cursor. -->
    <attr name="textCursorDrawable" format="reference" />

    <!-- Indicates that the content of a non-editable TextView can be selected.
     Default value is false. EditText content is always selectable. -->
    <attr name="textIsSelectable" format="boolean" />
@@ -2783,6 +2786,9 @@
        <!-- Variation of textEditSidePasteWindowLayout displayed when the clipboard is empty. -->
        <attr name="textEditSideNoPasteWindowLayout" />

        <!-- Reference to a drawable that will be drawn under the insertion cursor. -->
        <attr name="textCursorDrawable" />

        <!-- Indicates that the content of a non-editable text can be selected. -->
        <attr name="textIsSelectable" />
    </declare-styleable>
Loading