Loading core/java/android/text/Layout.java +3 −3 Original line number Diff line number Diff line Loading @@ -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) { Loading Loading @@ -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); Loading core/java/android/widget/TextView.java +122 −43 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 Loading @@ -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); } } } } Loading Loading @@ -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); } } } Loading Loading @@ -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 Loading @@ -4366,6 +4392,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mHighlightPathBogus) { mHighlightPath.reset(); mLayout.getCursorPath(selStart, mHighlightPath, mText); updateCursorsPositions(); mHighlightPathBogus = false; } Loading @@ -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(); Loading Loading @@ -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()) { Loading @@ -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. Loading Loading @@ -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; } Loading @@ -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; } Loading @@ -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; } Loading Loading @@ -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; Loading core/res/res/drawable-mdpi/text_cursor_holo_dark.9.png 0 → 100644 +274 B Loading image diff... core/res/res/drawable-mdpi/text_cursor_holo_light.9.png 0 → 100644 +249 B Loading image diff... core/res/res/values/attrs.xml +6 −0 Original line number Diff line number Diff line Loading @@ -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" /> Loading Loading @@ -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 Loading
core/java/android/text/Layout.java +3 −3 Original line number Diff line number Diff line Loading @@ -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) { Loading Loading @@ -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); Loading
core/java/android/widget/TextView.java +122 −43 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 Loading @@ -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); } } } } Loading Loading @@ -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); } } } Loading Loading @@ -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 Loading @@ -4366,6 +4392,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mHighlightPathBogus) { mHighlightPath.reset(); mLayout.getCursorPath(selStart, mHighlightPath, mText); updateCursorsPositions(); mHighlightPathBogus = false; } Loading @@ -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(); Loading Loading @@ -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()) { Loading @@ -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. Loading Loading @@ -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; } Loading @@ -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; } Loading @@ -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; } Loading Loading @@ -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; Loading
core/res/res/values/attrs.xml +6 −0 Original line number Diff line number Diff line Loading @@ -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" /> Loading Loading @@ -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