diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index 8700af802ae64..97a216a8415d0 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -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); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 28b106bc4be4b..993af31c31cd6 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -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,33 +3778,40 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mHighlightPathBogus) { invalidateCursor(); } else { - synchronized (sTempRect) { - /* - * The reason for this concern about the thickness of the - * cursor and doing the floor/ceil on the coordinates is that - * some EditTexts (notably textfields in the Browser) have - * anti-aliased text where not all the characters are - * necessarily at integer-multiple locations. This should - * make sure the entire cursor gets invalidated instead of - * sometimes missing half a pixel. - */ + final int horizontalPadding = getCompoundPaddingLeft(); + final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); - float thick = FloatMath.ceil(mTextPaint.getStrokeWidth()); - if (thick < 1.0f) { - thick = 1.0f; + if (mCursorCount == 0) { + synchronized (sTempRect) { + /* + * The reason for this concern about the thickness of the + * cursor and doing the floor/ceil on the coordinates is that + * some EditTexts (notably textfields in the Browser) have + * anti-aliased text where not all the characters are + * necessarily at integer-multiple locations. This should + * 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.0f; + + mHighlightPath.computeBounds(sTempRect, false); + + 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); } - - thick /= 2; - - 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)); } } } @@ -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,7 +4404,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } mHighlightPaint.setStyle(Paint.Style.STROKE); - highlight = mHighlightPath; + if (mCursorCount > 0) { + drawCursor = true; + } else { + highlight = mHighlightPath; + } } } else { if (mHighlightPathBogus) { @@ -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; diff --git a/core/res/res/drawable-mdpi/text_cursor_holo_dark.9.png b/core/res/res/drawable-mdpi/text_cursor_holo_dark.9.png new file mode 100644 index 0000000000000..b9435b6451177 Binary files /dev/null and b/core/res/res/drawable-mdpi/text_cursor_holo_dark.9.png differ diff --git a/core/res/res/drawable-mdpi/text_cursor_holo_light.9.png b/core/res/res/drawable-mdpi/text_cursor_holo_light.9.png new file mode 100644 index 0000000000000..477d820cc965f Binary files /dev/null and b/core/res/res/drawable-mdpi/text_cursor_holo_light.9.png differ diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 8802003af5180..6f37dc0970105 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -778,6 +778,9 @@ + + + @@ -2783,6 +2786,9 @@ + + + diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index aaf071b329ec9..454257522a09c 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -1642,4 +1642,7 @@ + + + diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 5700641f3986a..8cc5944dc0588 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -426,6 +426,7 @@ ?android:attr/textEditNoPasteWindowLayout ?android:attr/textEditSidePasteWindowLayout ?android:attr/textEditSideNoPasteWindowLayout + ?android:attr/textCursorDrawable