From 8b94a32a1105c0584cb576709baca837ded40d8e Mon Sep 17 00:00:00 2001 From: "lambroslambrou@chromium.org" Date: Thu, 21 Nov 2013 06:54:23 +0000 Subject: Allow user to fling the mouse-cursor in Chromoting Android client. BUG=315174 Review URL: https://codereview.chromium.org/77143002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@236425 0039d316-1c4b-4281-b951-d872f2087c98 --- .../src/org/chromium/chromoting/DesktopView.java | 58 +++++++++++++++++----- .../chromium/chromoting/DesktopViewInterface.java | 6 +++ .../org/chromium/chromoting/TouchInputHandler.java | 8 ++- .../chromium/chromoting/TrackingInputHandler.java | 53 +++++++++++++++++++- 4 files changed, 111 insertions(+), 14 deletions(-) (limited to 'remoting/android') diff --git a/remoting/android/java/src/org/chromium/chromoting/DesktopView.java b/remoting/android/java/src/org/chromium/chromoting/DesktopView.java index 3a4f30c..5e04f23 100644 --- a/remoting/android/java/src/org/chromium/chromoting/DesktopView.java +++ b/remoting/android/java/src/org/chromium/chromoting/DesktopView.java @@ -106,6 +106,14 @@ public class DesktopView extends SurfaceView implements DesktopViewInterface, Ru private FeedbackAnimator mFeedbackAnimator = new FeedbackAnimator(); + // Variables to control animation by the TouchInputHandler. + + /** Protects mInputAnimationRunning. */ + private Object mAnimationLock = new Object(); + + /** Whether the TouchInputHandler has requested animation to be performed. */ + private boolean mInputAnimationRunning = false; + public DesktopView(Activity context) { super(context); @@ -120,9 +128,7 @@ public class DesktopView extends SurfaceView implements DesktopViewInterface, Ru getHolder().addCallback(this); } - /** - * Request repainting of the desktop view. - */ + /** Request repainting of the desktop view. */ void requestRepaint() { synchronized (mRenderData) { if (mRepaintPending) { @@ -187,20 +193,13 @@ public class DesktopView extends SurfaceView implements DesktopViewInterface, Ru canvas.drawColor(Color.BLACK); canvas.drawBitmap(image, 0, 0, new Paint()); - if (mFeedbackAnimator.isAnimationRunning()) { + boolean feedbackAnimationRunning = mFeedbackAnimator.isAnimationRunning(); + if (feedbackAnimationRunning) { float scaleFactor; synchronized (mRenderData) { scaleFactor = mRenderData.transform.mapRadius(1); } mFeedbackAnimator.render(canvas, x, y, 40 / scaleFactor); - - // Trigger a repaint request for the next frame of the animation. - getHandler().postAtTime(new Runnable() { - @Override - public void run() { - requestRepaint(); - } - }, startTimeMs + 30); } Bitmap cursorBitmap = JniInterface.getCursorBitmap(); @@ -210,6 +209,31 @@ public class DesktopView extends SurfaceView implements DesktopViewInterface, Ru } getHolder().unlockCanvasAndPost(canvas); + + synchronized (mAnimationLock) { + if (mInputAnimationRunning || feedbackAnimationRunning) { + getHandler().postAtTime(new Runnable() { + @Override + public void run() { + processAnimation(); + } + }, startTimeMs + 30); + } + }; + } + + private void processAnimation() { + boolean running; + synchronized (mAnimationLock) { + running = mInputAnimationRunning; + } + if (running) { + mInputHandler.processAnimation(); + } + running |= mFeedbackAnimator.isAnimationRunning(); + if (running) { + requestRepaint(); + } } /** @@ -323,4 +347,14 @@ public class DesktopView extends SurfaceView implements DesktopViewInterface, Ru public void transformationChanged() { requestRepaint(); } + + @Override + public void setAnimationEnabled(boolean enabled) { + synchronized (mAnimationLock) { + if (enabled && !mInputAnimationRunning) { + requestRepaint(); + } + mInputAnimationRunning = enabled; + } + } } diff --git a/remoting/android/java/src/org/chromium/chromoting/DesktopViewInterface.java b/remoting/android/java/src/org/chromium/chromoting/DesktopViewInterface.java index 18995f3..c0d50a0 100644 --- a/remoting/android/java/src/org/chromium/chromoting/DesktopViewInterface.java +++ b/remoting/android/java/src/org/chromium/chromoting/DesktopViewInterface.java @@ -25,4 +25,10 @@ public interface DesktopViewInterface { * has been changed by the TouchInputHandler, which requires repainting. */ void transformationChanged(); + + /** + * Starts or stops an animation. Whilst the animation is running, the DesktopView will + * periodically call TouchInputHandler.processAnimation() and repaint itself. + */ + void setAnimationEnabled(boolean enabled); } diff --git a/remoting/android/java/src/org/chromium/chromoting/TouchInputHandler.java b/remoting/android/java/src/org/chromium/chromoting/TouchInputHandler.java index d546b47..bbbf4a6 100644 --- a/remoting/android/java/src/org/chromium/chromoting/TouchInputHandler.java +++ b/remoting/android/java/src/org/chromium/chromoting/TouchInputHandler.java @@ -11,7 +11,7 @@ import android.view.MotionEvent; * switched. The DesktopView passes the low-level touchscreen events and other events via this * interface. The implementation recognizes and processes the touchscreen gestures, and then * performs actions on the DesktopView (such as panning/zooming the display, injecting input, or - * showing/hiding UI elements). + * showing/hiding UI elements). All methods are called on the main UI thread. */ public interface TouchInputHandler { // These constants must match those in the generated struct protoc::MouseEvent_MouseButton. @@ -46,4 +46,10 @@ public interface TouchInputHandler { * method returns. */ void onHostSizeChanged(int width, int height); + + /** + * Whilst an animation is in progress, this method is called repeatedly until the animation is + * cancelled. After this method returns, the DesktopView will schedule a repaint. + */ + void processAnimation(); } diff --git a/remoting/android/java/src/org/chromium/chromoting/TrackingInputHandler.java b/remoting/android/java/src/org/chromium/chromoting/TrackingInputHandler.java index 05e22db..daa4eb5 100644 --- a/remoting/android/java/src/org/chromium/chromoting/TrackingInputHandler.java +++ b/remoting/android/java/src/org/chromium/chromoting/TrackingInputHandler.java @@ -11,6 +11,7 @@ import android.util.Log; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.ScaleGestureDetector; +import android.widget.Scroller; /** * This class implements the cursor-tracking behavior and gestures. @@ -34,6 +35,9 @@ public class TrackingInputHandler implements TouchInputHandler { private ScaleGestureDetector mZoomer; private TapGestureDetector mTapDetector; + /** Used to calculate the physics for flinging the cursor. */ + private Scroller mFlingScroller; + /** * The current cursor position is stored here as floats, so that the desktop image can be * positioned with sub-pixel accuracy, to give a smoother panning animation at high zoom levels. @@ -78,6 +82,8 @@ public class TrackingInputHandler implements TouchInputHandler { mZoomer = new ScaleGestureDetector(context, listener); mTapDetector = new TapGestureDetector(context, listener); + mFlingScroller = new Scroller(context); + mCursorPosition = new PointF(); // The threshold needs to be bigger than the ScaledTouchSlop used by the gesture-detectors, @@ -214,7 +220,6 @@ public class TrackingInputHandler implements TouchInputHandler { return false; } - /** Injects a button-up event if the button is currently held down (during a drag event). */ private void releaseAnyHeldButton() { if (mHeldButton != BUTTON_UNDEFINED) { @@ -232,6 +237,10 @@ public class TrackingInputHandler implements TouchInputHandler { handled |= mTapDetector.onTouchEvent(event); switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + mViewer.setAnimationEnabled(false); + break; + case MotionEvent.ACTION_POINTER_DOWN: mTotalMotionY = 0; mSwipeCompleted = false; @@ -262,6 +271,26 @@ public class TrackingInputHandler implements TouchInputHandler { repositionImageWithZoom(); } + @Override + public void processAnimation() { + int previousX = mFlingScroller.getCurrX(); + int previousY = mFlingScroller.getCurrY(); + if (!mFlingScroller.computeScrollOffset()) { + mViewer.setAnimationEnabled(false); + return; + } + int deltaX = mFlingScroller.getCurrX() - previousX; + int deltaY = mFlingScroller.getCurrY() - previousY; + float delta[] = {(float)deltaX, (float)deltaY}; + synchronized (mRenderData) { + Matrix canvasToImage = new Matrix(); + mRenderData.transform.invert(canvasToImage); + canvasToImage.mapVectors(delta); + } + + moveCursor(mCursorPosition.x + delta[0], mCursorPosition.y + delta[1]); + } + /** Responds to touch events filtered by the gesture detectors. */ private class GestureListener extends GestureDetector.SimpleOnGestureListener implements ScaleGestureDetector.OnScaleGestureListener, @@ -297,6 +326,28 @@ public class TrackingInputHandler implements TouchInputHandler { return true; } + /** + * Called when a fling gesture is recognized. + */ + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + // The fling physics calculation is based on screen coordinates, so that it will + // behave consistently at different zoom levels (and will work nicely at high zoom + // levels, since |mFlingScroller| outputs integer coordinates). However, the desktop + // will usually be panned as the cursor is moved across the desktop, which means the + // transformation mapping from screen to desktop coordinates will change. To deal with + // this, the cursor movement is computed from relative coordinate changes from + // |mFlingScroller|. This means the fling can be started at (0, 0) with no bounding + // constraints - the cursor is already constrained by the desktop size. + mFlingScroller.fling(0, 0, (int)velocityX, (int)velocityY, Integer.MIN_VALUE, + Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE); + // Initialize the scroller's current offset coordinates, since they are used for + // calculating the delta values. + mFlingScroller.computeScrollOffset(); + mViewer.setAnimationEnabled(true); + return true; + } + /** Called when the user is in the process of pinch-zooming. */ @Override public boolean onScale(ScaleGestureDetector detector) { -- cgit v1.1