Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(330)

Side by Side Diff: content/public/android/java/src/org/chromium/content/browser/input/HandleView.java

Issue 335943002: [Android] Composited selection handle rendering (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@input_native_handles_final
Patch Set: Fix animation tests Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.content.browser.input;
6
7 import android.content.Context;
8 import android.content.res.TypedArray;
9 import android.graphics.Canvas;
10 import android.graphics.Rect;
11 import android.graphics.drawable.Drawable;
12 import android.os.SystemClock;
13 import android.util.TypedValue;
14 import android.view.MotionEvent;
15 import android.view.View;
16 import android.view.ViewConfiguration;
17 import android.view.ViewParent;
18 import android.view.animation.AnimationUtils;
19 import android.widget.PopupWindow;
20
21 import org.chromium.content.browser.PositionObserver;
22
23 /**
24 * View that displays a selection or insertion handle for text editing.
25 *
26 * While a HandleView is logically a child of some other view, it does not exist in that View's
27 * hierarchy.
28 *
29 */
30 public class HandleView extends View {
31 private static final float FADE_DURATION = 200.f;
32
33 private Drawable mDrawable;
34 private final PopupWindow mContainer;
35
36 // The position of the handle relative to the parent view.
37 private int mPositionX;
38 private int mPositionY;
39
40 // The position of the parent relative to the application's root view.
41 private int mParentPositionX;
42 private int mParentPositionY;
43
44 // The offset from this handles position to the "tip" of the handle.
45 private float mHotspotX;
46 private float mHotspotY;
47
48 private final CursorController mController;
49 private boolean mIsDragging;
50 private float mTouchToWindowOffsetX;
51 private float mTouchToWindowOffsetY;
52
53 private final int mLineOffsetY;
54 private float mDownPositionX, mDownPositionY;
55 private long mTouchTimer;
56 private boolean mIsInsertionHandle = false;
57 private float mAlpha;
58 private long mFadeStartTime;
59
60 private final View mParent;
61 private InsertionHandleController.PastePopupMenu mPastePopupWindow;
62
63 private final Rect mTempRect = new Rect();
64
65 static final int LEFT = 0;
66 static final int CENTER = 1;
67 static final int RIGHT = 2;
68
69 private final PositionObserver mParentPositionObserver;
70 private final PositionObserver.Listener mParentPositionListener;
71
72 // Number of dips to subtract from the handle's y position to give a suitabl e
73 // y coordinate for the corresponding text position. This is to compensate f or the fact
74 // that the handle position is at the base of the line of text.
75 private static final float LINE_OFFSET_Y_DIP = 5.0f;
76
77 private static final int[] TEXT_VIEW_HANDLE_ATTRS = {
78 android.R.attr.textSelectHandleLeft,
79 android.R.attr.textSelectHandle,
80 android.R.attr.textSelectHandleRight,
81 };
82
83 HandleView(CursorController controller, int pos, View parent,
84 PositionObserver parentPositionObserver) {
85 super(parent.getContext());
86 mParent = parent;
87 Context context = mParent.getContext();
88 mController = controller;
89 mContainer = new PopupWindow(context, null, android.R.attr.textSelectHan dleWindowStyle);
90 mContainer.setSplitTouchEnabled(true);
91 mContainer.setClippingEnabled(false);
92 mContainer.setAnimationStyle(0);
93
94 setOrientation(pos);
95
96 // Convert line offset dips to pixels.
97 mLineOffsetY = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_D IP,
98 LINE_OFFSET_Y_DIP, context.getResources().getDisplayMetrics());
99
100 mAlpha = 1.f;
101
102 mParentPositionListener = new PositionObserver.Listener() {
103 @Override
104 public void onPositionChanged(int x, int y) {
105 updateParentPosition(x, y);
106 }
107 };
108 mParentPositionObserver = parentPositionObserver;
109 }
110
111 void setOrientation(int pos) {
112 Context context = mParent.getContext();
113 TypedArray a = context.getTheme().obtainStyledAttributes(TEXT_VIEW_HANDL E_ATTRS);
114 mDrawable = a.getDrawable(pos);
115 a.recycle();
116
117 mIsInsertionHandle = (pos == CENTER);
118
119 int handleWidth = mDrawable.getIntrinsicWidth();
120 switch (pos) {
121 case LEFT: {
122 mHotspotX = (handleWidth * 3) / 4f;
123 break;
124 }
125
126 case RIGHT: {
127 mHotspotX = handleWidth / 4f;
128 break;
129 }
130
131 case CENTER:
132 default: {
133 mHotspotX = handleWidth / 2f;
134 break;
135 }
136 }
137 mHotspotY = 0;
138
139 invalidate();
140 }
141
142 @Override
143 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
144 setMeasuredDimension(mDrawable.getIntrinsicWidth(),
145 mDrawable.getIntrinsicHeight());
146 }
147
148 private void updateParentPosition(int parentPositionX, int parentPositionY) {
149 // Hide paste popup window as soon as a scroll occurs.
150 if (mPastePopupWindow != null) mPastePopupWindow.hide();
151
152 mTouchToWindowOffsetX += parentPositionX - mParentPositionX;
153 mTouchToWindowOffsetY += parentPositionY - mParentPositionY;
154 mParentPositionX = parentPositionX;
155 mParentPositionY = parentPositionY;
156 onPositionChanged();
157 }
158
159 private int getContainerPositionX() {
160 return mParentPositionX + mPositionX;
161 }
162
163 private int getContainerPositionY() {
164 return mParentPositionY + mPositionY;
165 }
166
167 private void onPositionChanged() {
168 // Deferring View invalidation while the handles are hidden prevents
169 // scheduling conflicts with the compositor.
170 if (getVisibility() != VISIBLE) return;
171 mContainer.update(getContainerPositionX(), getContainerPositionY(),
172 getRight() - getLeft(), getBottom() - getTop());
173 }
174
175 private void showContainer() {
176 mContainer.showAtLocation(mParent, 0, getContainerPositionX(), getContai nerPositionY());
177 }
178
179 void show() {
180 // While hidden, the parent position may have become stale. It must be u pdated before
181 // checking isPositionVisible().
182 updateParentPosition(mParentPositionObserver.getPositionX(),
183 mParentPositionObserver.getPositionY());
184 if (!isPositionVisible()) {
185 hide();
186 return;
187 }
188 mParentPositionObserver.addListener(mParentPositionListener);
189 mContainer.setContentView(this);
190 showContainer();
191
192 // Hide paste view when handle is moved on screen.
193 if (mPastePopupWindow != null) {
194 mPastePopupWindow.hide();
195 }
196 }
197
198 void hide() {
199 mIsDragging = false;
200 mContainer.dismiss();
201 mParentPositionObserver.removeListener(mParentPositionListener);
202 if (mPastePopupWindow != null) {
203 mPastePopupWindow.hide();
204 }
205 }
206
207 boolean isShowing() {
208 return mContainer.isShowing();
209 }
210
211 private boolean isPositionVisible() {
212 // Always show a dragging handle.
213 if (mIsDragging) {
214 return true;
215 }
216
217 final Rect clip = mTempRect;
218 clip.left = 0;
219 clip.top = 0;
220 clip.right = mParent.getWidth();
221 clip.bottom = mParent.getHeight();
222
223 final ViewParent parent = mParent.getParent();
224 if (parent == null || !parent.getChildVisibleRect(mParent, clip, null)) {
225 return false;
226 }
227
228 final int posX = getContainerPositionX() + (int) mHotspotX;
229 final int posY = getContainerPositionY() + (int) mHotspotY;
230
231 return posX >= clip.left && posX <= clip.right &&
232 posY >= clip.top && posY <= clip.bottom;
233 }
234
235 // x and y are in physical pixels.
236 void moveTo(int x, int y) {
237 int previousPositionX = mPositionX;
238 int previousPositionY = mPositionY;
239
240 mPositionX = x;
241 mPositionY = y;
242 if (isPositionVisible()) {
243 if (mContainer.isShowing()) {
244 onPositionChanged();
245 // Hide paste popup window as soon as the handle is dragged.
246 if (mPastePopupWindow != null &&
247 (previousPositionX != mPositionX || previousPositionY != mPositionY)) {
248 mPastePopupWindow.hide();
249 }
250 } else {
251 show();
252 }
253
254 if (mIsDragging) {
255 // Hide paste popup window as soon as the handle is dragged.
256 if (mPastePopupWindow != null) {
257 mPastePopupWindow.hide();
258 }
259 }
260 } else {
261 hide();
262 }
263 }
264
265 @Override
266 protected void onDraw(Canvas c) {
267 updateAlpha();
268 mDrawable.setBounds(0, 0, getRight() - getLeft(), getBottom() - getTop() );
269 mDrawable.draw(c);
270 }
271
272 @Override
273 public boolean onTouchEvent(MotionEvent ev) {
274 switch (ev.getActionMasked()) {
275 case MotionEvent.ACTION_DOWN: {
276 mDownPositionX = ev.getRawX();
277 mDownPositionY = ev.getRawY();
278 mTouchToWindowOffsetX = mDownPositionX - mPositionX;
279 mTouchToWindowOffsetY = mDownPositionY - mPositionY;
280 mIsDragging = true;
281 mController.beforeStartUpdatingPosition(this);
282 mTouchTimer = SystemClock.uptimeMillis();
283 break;
284 }
285
286 case MotionEvent.ACTION_MOVE: {
287 updatePosition(ev.getRawX(), ev.getRawY());
288 break;
289 }
290
291 case MotionEvent.ACTION_UP:
292 if (mIsInsertionHandle) {
293 long delay = SystemClock.uptimeMillis() - mTouchTimer;
294 if (delay < ViewConfiguration.getTapTimeout()) {
295 if (mPastePopupWindow != null && mPastePopupWindow.isSho wing()) {
296 // Tapping on the handle dismisses the displayed pas te view,
297 mPastePopupWindow.hide();
298 } else {
299 showPastePopupWindow();
300 }
301 }
302 }
303 mIsDragging = false;
304 break;
305
306 case MotionEvent.ACTION_CANCEL:
307 mIsDragging = false;
308 break;
309
310 default:
311 return false;
312 }
313 return true;
314 }
315
316 boolean isDragging() {
317 return mIsDragging;
318 }
319
320 /**
321 * @return Returns the x position of the handle
322 */
323 int getPositionX() {
324 return mPositionX;
325 }
326
327 /**
328 * @return Returns the y position of the handle
329 */
330 int getPositionY() {
331 return mPositionY;
332 }
333
334 private void updatePosition(float rawX, float rawY) {
335 final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX;
336 final float newPosY = rawY - mTouchToWindowOffsetY + mHotspotY - mLineOf fsetY;
337
338 mController.updatePosition(this, Math.round(newPosX), Math.round(newPosY ));
339 }
340
341 // x and y are in physical pixels.
342 void positionAt(int x, int y) {
343 moveTo(x - Math.round(mHotspotX), y - Math.round(mHotspotY));
344 }
345
346 // Returns the x coordinate of the position that the handle appears to be po inting to relative
347 // to the handles "parent" view.
348 int getAdjustedPositionX() {
349 return mPositionX + Math.round(mHotspotX);
350 }
351
352 // Returns the y coordinate of the position that the handle appears to be po inting to relative
353 // to the handles "parent" view.
354 int getAdjustedPositionY() {
355 return mPositionY + Math.round(mHotspotY);
356 }
357
358 // Returns the x coordinate of the postion that the handle appears to be poi nting to relative to
359 // the root view of the application.
360 int getRootViewRelativePositionX() {
361 return getContainerPositionX() + Math.round(mHotspotX);
362 }
363
364 // Returns the y coordinate of the postion that the handle appears to be poi nting to relative to
365 // the root view of the application.
366 int getRootViewRelativePositionY() {
367 return getContainerPositionY() + Math.round(mHotspotY);
368 }
369
370 // Returns a suitable y coordinate for the text position corresponding to th e handle.
371 // As the handle points to a position on the base of the line of text, this method
372 // returns a coordinate a small number of pixels higher (i.e. a slightly sma ller number)
373 // than getAdjustedPositionY.
374 int getLineAdjustedPositionY() {
375 return (int) (mPositionY + mHotspotY - mLineOffsetY);
376 }
377
378 Drawable getDrawable() {
379 return mDrawable;
380 }
381
382 private void updateAlpha() {
383 if (mAlpha == 1.f) return;
384 mAlpha = Math.min(1.f,
385 (AnimationUtils.currentAnimationTimeMillis() - mFadeStartTime) / FADE_DURATION);
386 mDrawable.setAlpha((int) (255 * mAlpha));
387 invalidate();
388 }
389
390 /**
391 * If the handle is not visible, sets its visibility to View.VISIBLE and beg ins fading it in.
392 */
393 void beginFadeIn() {
394 if (getVisibility() == VISIBLE) return;
395 mAlpha = 0.f;
396 mFadeStartTime = AnimationUtils.currentAnimationTimeMillis();
397 setVisibility(VISIBLE);
398 // Position updates may have been deferred while the handle was hidden.
399 onPositionChanged();
400 }
401
402 void showPastePopupWindow() {
403 InsertionHandleController ihc = (InsertionHandleController) mController;
404 if (mIsInsertionHandle && ihc.canPaste()) {
405 if (mPastePopupWindow == null) {
406 // Lazy initialization: create when actually shown only.
407 mPastePopupWindow = ihc.new PastePopupMenu();
408 }
409 mPastePopupWindow.show();
410 }
411 }
412 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698