diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index 123accae4f6e3..a6e8c7032eaf4 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -792,8 +792,17 @@ public abstract class Layout { * the paragraph's primary direction. */ public float getPrimaryHorizontal(int offset) { + return getPrimaryHorizontal(offset, false /* not clamped */); + } + + /** + * Get the primary horizontal position for the specified text offset, but + * optionally clamp it so that it doesn't exceed the width of the layout. + * @hide + */ + public float getPrimaryHorizontal(int offset, boolean clamped) { boolean trailing = primaryIsTrailingPrevious(offset); - return getHorizontal(offset, trailing); + return getHorizontal(offset, trailing, clamped); } /** @@ -802,17 +811,26 @@ public abstract class Layout { * the direction other than the paragraph's primary direction. */ public float getSecondaryHorizontal(int offset) { - boolean trailing = primaryIsTrailingPrevious(offset); - return getHorizontal(offset, !trailing); + return getSecondaryHorizontal(offset, false /* not clamped */); } - private float getHorizontal(int offset, boolean trailing) { + /** + * Get the secondary horizontal position for the specified text offset, but + * optionally clamp it so that it doesn't exceed the width of the layout. + * @hide + */ + public float getSecondaryHorizontal(int offset, boolean clamped) { + boolean trailing = primaryIsTrailingPrevious(offset); + return getHorizontal(offset, !trailing, clamped); + } + + private float getHorizontal(int offset, boolean trailing, boolean clamped) { int line = getLineForOffset(offset); - return getHorizontal(offset, trailing, line); + return getHorizontal(offset, trailing, line, clamped); } - private float getHorizontal(int offset, boolean trailing, int line) { + private float getHorizontal(int offset, boolean trailing, int line, boolean clamped) { int start = getLineStart(line); int end = getLineEnd(line); int dir = getParagraphDirection(line); @@ -834,6 +852,9 @@ public abstract class Layout { float wid = tl.measure(offset - start, trailing, null); TextLine.recycle(tl); + if (clamped && wid > mWidth) { + wid = mWidth; + } int left = getParagraphLeft(line); int right = getParagraphRight(line); @@ -1256,6 +1277,23 @@ public abstract class Layout { return offset; } + /** + * Determine whether we should clamp cursor position. Currently it's + * only robust for left-aligned displays. + * @hide + */ + public boolean shouldClampCursor(int line) { + // Only clamp cursor position in left-aligned displays. + switch (getParagraphAlignment(line)) { + case ALIGN_LEFT: + return true; + case ALIGN_NORMAL: + return getParagraphDirection(line) > 0; + default: + return false; + } + + } /** * Fills in the specified Path with a representation of a cursor * at the specified offset. This will often be a vertical line @@ -1270,8 +1308,9 @@ public abstract class Layout { int top = getLineTop(line); int bottom = getLineTop(line+1); - float h1 = getPrimaryHorizontal(point) - 0.5f; - float h2 = isLevelBoundary(point) ? getSecondaryHorizontal(point) - 0.5f : h1; + boolean clamped = shouldClampCursor(line); + float h1 = getPrimaryHorizontal(point, clamped) - 0.5f; + float h2 = isLevelBoundary(point) ? getSecondaryHorizontal(point, clamped) - 0.5f : h1; int caps = TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_SHIFT_ON) | TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_SELECTING); @@ -1357,8 +1396,8 @@ public abstract class Layout { int en = Math.min(end, there); if (st != en) { - float h1 = getHorizontal(st, false, line); - float h2 = getHorizontal(en, true, line); + float h1 = getHorizontal(st, false, line, false /* not clamped */); + float h2 = getHorizontal(en, true, line, false /* not clamped */); float left = Math.min(h1, h2); float right = Math.max(h1, h2); diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index ef95af76c623c..41d60339b5c7d 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -1468,20 +1468,24 @@ public class Editor { middle = (top + bottom) >> 1; } - updateCursorPosition(0, top, middle, getPrimaryHorizontal(layout, hintLayout, offset)); + boolean clamped = layout.shouldClampCursor(line); + updateCursorPosition(0, top, middle, + getPrimaryHorizontal(layout, hintLayout, offset, clamped)); if (mCursorCount == 2) { - updateCursorPosition(1, middle, bottom, layout.getSecondaryHorizontal(offset)); + updateCursorPosition(1, middle, bottom, + layout.getSecondaryHorizontal(offset, clamped)); } } - private float getPrimaryHorizontal(Layout layout, Layout hintLayout, int offset) { + private float getPrimaryHorizontal(Layout layout, Layout hintLayout, int offset, + boolean clamped) { if (TextUtils.isEmpty(layout.getText()) && hintLayout != null && !TextUtils.isEmpty(hintLayout.getText())) { - return hintLayout.getPrimaryHorizontal(offset); + return hintLayout.getPrimaryHorizontal(offset, clamped); } else { - return layout.getPrimaryHorizontal(offset); + return layout.getPrimaryHorizontal(offset, clamped); } } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 0641df6f5f27e..d635de6f1edcf 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -6553,15 +6553,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener int line = layout.getLineForOffset(offset); - // FIXME: Is it okay to truncate this, or should we round? - final int x = (int)layout.getPrimaryHorizontal(offset); - final int top = layout.getLineTop(line); - final int bottom = layout.getLineTop(line + 1); - - int left = (int) FloatMath.floor(layout.getLineLeft(line)); - int right = (int) FloatMath.ceil(layout.getLineRight(line)); - int ht = layout.getHeight(); - int grav; switch (layout.getParagraphAlignment(line)) { @@ -6583,8 +6574,32 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener break; } + // We only want to clamp the cursor to fit within the layout width + // in left-to-right modes, because in a right to left alignment, + // we want to scroll to keep the line-right on the screen, as other + // lines are likely to have text flush with the right margin, which + // we want to keep visible. + // A better long-term solution would probably be to measure both + // the full line and a blank-trimmed version, and, for example, use + // the latter measurement for centering and right alignment, but for + // the time being we only implement the cursor clamping in left to + // right where it is most likely to be annoying. + final boolean clamped = grav > 0; + // FIXME: Is it okay to truncate this, or should we round? + final int x = (int)layout.getPrimaryHorizontal(offset, clamped); + final int top = layout.getLineTop(line); + final int bottom = layout.getLineTop(line + 1); + + int left = (int) FloatMath.floor(layout.getLineLeft(line)); + int right = (int) FloatMath.ceil(layout.getLineRight(line)); + int ht = layout.getHeight(); + int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); + if (!mHorizontallyScrolling && right - left > hspace && right > x) { + // If cursor has been clamped, make sure we don't scroll. + right = Math.max(x, left + hspace); + } int hslack = (bottom - top) / 2; int vslack = hslack;