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 Original line 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).
     * are at different run levels (and thus there's a split caret).
     * @param offset the offset
     * @param offset the offset
     * @return true if at a level boundary
     * @return true if at a level boundary
     * @hide
     */
     */
    private boolean isLevelBoundary(int offset) {
    public boolean isLevelBoundary(int offset) {
        int line = getLineForOffset(offset);
        int line = getLineForOffset(offset);
        Directions dirs = getLineDirections(line);
        Directions dirs = getLineDirections(line);
        if (dirs == DIRS_ALL_LEFT_TO_RIGHT || dirs == DIRS_ALL_RIGHT_TO_LEFT) {
        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);
        int bottom = getLineTop(line+1);


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


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


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


    Drawable mSelectHandleLeft;
    private int mCursorDrawableRes;
    Drawable mSelectHandleRight;
    private final Drawable[] mCursorDrawable = new Drawable[2];
    Drawable mSelectHandleCenter;
    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 int mLastDownPositionX, mLastDownPositionY;
    private Callback mCustomSelectionActionModeCallback;
    private Callback mCustomSelectionActionModeCallback;
@@ -742,6 +746,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                }
                }
                break;
                break;


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

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

            if (mCursorCount == 0) {
                synchronized (sTempRect) {
                synchronized (sTempRect) {
                    /*
                    /*
                     * The reason for this concern about the thickness of the
                     * 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
                     * make sure the entire cursor gets invalidated instead of
                     * sometimes missing half a pixel.
                     * sometimes missing half a pixel.
                     */
                     */

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


                thick /= 2;
                    thick /= 2.0f;


                    mHighlightPath.computeBounds(sTempRect, false);
                    mHighlightPath.computeBounds(sTempRect, false);


                int left = getCompoundPaddingLeft();
                    invalidate((int) FloatMath.floor(horizontalPadding + sTempRect.left - thick),
                int top = getExtendedPaddingTop() + getVerticalOffset(true);
                            (int) FloatMath.floor(verticalPadding + sTempRect.top - thick),

                            (int) FloatMath.ceil(horizontalPadding + sTempRect.right + thick),
                invalidate((int) FloatMath.floor(left + sTempRect.left - thick),
                            (int) FloatMath.ceil(verticalPadding + sTempRect.bottom + thick));
                           (int) FloatMath.floor(top + sTempRect.top - thick),
                }
                           (int) FloatMath.ceil(left + sTempRect.right + thick),
            } else {
                           (int) FloatMath.ceil(top + sTempRect.bottom + thick));
                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);
                    line2 = mLayout.getLineForOffset(last);


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


                int left = getCompoundPaddingLeft() + mScrollX;
                final int horizontalPadding = getCompoundPaddingLeft();
                invalidate(left, top + voffset + getExtendedPaddingTop(),
                final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
                           left + getWidth() - getCompoundPaddingLeft() -
                
                           getCompoundPaddingRight(),
                // If used, the cursor drawables can have an arbitrary dimension that can go beyond
                           bottom + voffset + getExtendedPaddingTop());
                // 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;
        Path highlight = null;
        int selStart = -1, selEnd = -1;
        int selStart = -1, selEnd = -1;
        boolean drawCursor = false;


        //  If there is no movement method, then there can be no selection.
        //  If there is no movement method, then there can be no selection.
        //  Check that first and attempt to skip everything having to do with
        //  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) {
                        if (mHighlightPathBogus) {
                            mHighlightPath.reset();
                            mHighlightPath.reset();
                            mLayout.getCursorPath(selStart, mHighlightPath, mText);
                            mLayout.getCursorPath(selStart, mHighlightPath, mText);
                            updateCursorsPositions();
                            mHighlightPathBogus = false;
                            mHighlightPathBogus = false;
                        }
                        }


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


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


        if (drawCursor) drawCursor(canvas, cursorOffsetVertical);

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


        if (mMarquee != null && mMarquee.shouldDrawGhost()) {
        if (mMarquee != null && mMarquee.shouldDrawGhost()) {
@@ -4478,6 +4511,52 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        updateCursorControllerPositions();
        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,
     * Update the positions of the CursorControllers.  Needed by WebTextView,
     * which does not draw.
     * which does not draw.
@@ -8699,7 +8778,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                }
                }
                mDrawable = mSelectHandleLeft;
                mDrawable = mSelectHandleLeft;
                handleWidth = mDrawable.getIntrinsicWidth();
                handleWidth = mDrawable.getIntrinsicWidth();
                mHotspotX = (handleWidth * 3) / 4;
                mHotspotX = handleWidth * 3.0f / 4.0f;
                break;
                break;
            }
            }


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


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


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


            bounds.right = bounds.left + width;
            bounds.right = bounds.left + width;
+274 B
Loading image diff...
+249 B
Loading image diff...
+6 −0
Original line number Original line Diff line number Diff line
@@ -778,6 +778,9 @@
    <!-- Color of link text (URLs). -->
    <!-- Color of link text (URLs). -->
    <attr name="textColorLink" format="reference|color" />
    <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.
    <!-- Indicates that the content of a non-editable TextView can be selected.
     Default value is false. EditText content is always selectable. -->
     Default value is false. EditText content is always selectable. -->
    <attr name="textIsSelectable" format="boolean" />
    <attr name="textIsSelectable" format="boolean" />
@@ -2783,6 +2786,9 @@
        <!-- Variation of textEditSidePasteWindowLayout displayed when the clipboard is empty. -->
        <!-- Variation of textEditSidePasteWindowLayout displayed when the clipboard is empty. -->
        <attr name="textEditSideNoPasteWindowLayout" />
        <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. -->
        <!-- Indicates that the content of a non-editable text can be selected. -->
        <attr name="textIsSelectable" />
        <attr name="textIsSelectable" />
    </declare-styleable>
    </declare-styleable>
Loading