summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Gilles Debunne <debunne@google.com> 2011-02-15 10:41:30 -0800
committer Android (Google) Code Review <android-gerrit@google.com> 2011-02-15 10:41:30 -0800
commit05cc6dbf70f3aab56c28300745cbbf9215977602 (patch)
treefa28e4b9f7d018e4f9eb2e08f16bc61c4d513472
parent0a20767f610e048b29b86e86c7e647d293be0308 (diff)
parentf75c97e023af7d4ad9a8c129d4ea282b1c3b8f94 (diff)
Merge "Text insertion cursor is now defined by a Drawable."
-rw-r--r--core/java/android/text/Layout.java6
-rw-r--r--core/java/android/widget/TextView.java165
-rw-r--r--core/res/res/drawable-mdpi/text_cursor_holo_dark.9.pngbin0 -> 274 bytes
-rw-r--r--core/res/res/drawable-mdpi/text_cursor_holo_light.9.pngbin0 -> 249 bytes
-rwxr-xr-xcore/res/res/values/attrs.xml6
-rw-r--r--core/res/res/values/public.xml3
-rw-r--r--core/res/res/values/styles.xml1
-rw-r--r--core/res/res/values/themes.xml3
8 files changed, 138 insertions, 46 deletions
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 8700af802ae6..97a216a8415d 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 28b106bc4be4..993af31c31cd 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.
- */
-
- float thick = FloatMath.ceil(mTextPaint.getStrokeWidth());
- if (thick < 1.0f) {
- thick = 1.0f;
- }
+ final int horizontalPadding = getCompoundPaddingLeft();
+ final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
- thick /= 2;
+ 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;
+ }
- mHighlightPath.computeBounds(sTempRect, false);
+ thick /= 2.0f;
- int left = getCompoundPaddingLeft();
- int top = getExtendedPaddingTop() + getVerticalOffset(true);
+ mHighlightPath.computeBounds(sTempRect, false);
- 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,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 000000000000..b9435b645117
--- /dev/null
+++ b/core/res/res/drawable-mdpi/text_cursor_holo_dark.9.png
Binary files 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 000000000000..477d820cc965
--- /dev/null
+++ b/core/res/res/drawable-mdpi/text_cursor_holo_light.9.png
Binary files differ
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 8802003af518..6f37dc097010 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -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>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index aaf071b329ec..454257522a09 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1642,4 +1642,7 @@
<!-- Default icon for applications that don't specify an icon. -->
<public type="mipmap" name="sym_def_app_icon" id="0x010d0000" />
+ <!-- Theme attribute used to customize the text insertion cursor -->
+ <!-- Commented out for HC MR1 to prevent an API change -->
+ <!-- <public type="attr" name="textCursorDrawable" id="0x01010362" /> -->
</resources>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 5700641f3986..8cc5944dc058 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -426,6 +426,7 @@
<item name="android:textEditNoPasteWindowLayout">?android:attr/textEditNoPasteWindowLayout</item>
<item name="android:textEditSidePasteWindowLayout">?android:attr/textEditSidePasteWindowLayout</item>
<item name="android:textEditSideNoPasteWindowLayout">?android:attr/textEditSideNoPasteWindowLayout</item>
+ <item name="android:textCursorDrawable">?android:attr/textCursorDrawable</item>
</style>
<style name="Widget.TextView.ListSeparator">
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 6d5b4822b1dc..927a668ac857 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -180,6 +180,7 @@
<item name="textEditNoPasteWindowLayout">@android:layout/text_edit_no_paste_window</item>
<item name="textEditSidePasteWindowLayout">@android:layout/text_edit_side_paste_window</item>
<item name="textEditSideNoPasteWindowLayout">@android:layout/text_edit_side_no_paste_window</item>
+ <item name="textCursorDrawable">@null</item>
<!-- Widget styles -->
<item name="absListViewStyle">@android:style/Widget.AbsListView</item>
@@ -906,6 +907,7 @@
<item name="textSelectHandleRight">@android:drawable/text_select_handle_right</item>
<item name="textSelectHandle">@android:drawable/text_select_handle_middle</item>
<item name="textSelectHandleWindowStyle">@android:style/Widget.Holo.TextSelectHandle</item>
+ <item name="textCursorDrawable">@android:drawable/text_cursor_holo_dark</item>
<!-- Widget styles -->
<item name="absListViewStyle">@android:style/Widget.Holo.AbsListView</item>
@@ -1181,6 +1183,7 @@
<item name="textSelectHandleRight">@android:drawable/text_select_handle_right</item>
<item name="textSelectHandle">@android:drawable/text_select_handle_middle</item>
<item name="textSelectHandleWindowStyle">@android:style/Widget.Holo.TextSelectHandle</item>
+ <item name="textCursorDrawable">@android:drawable/text_cursor_holo_light</item>
<!-- Widget styles -->
<item name="absListViewStyle">@android:style/Widget.Holo.Light.AbsListView</item>