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

Unified Diff: remoting/ios/ui/scene_view.mm

Issue 186733007: iOS Chromoting Client (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 7 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « remoting/ios/ui/scene_view.h ('k') | remoting/ios/ui/scene_view_unittest.mm » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: remoting/ios/ui/scene_view.mm
diff --git a/remoting/ios/ui/scene_view.mm b/remoting/ios/ui/scene_view.mm
new file mode 100644
index 0000000000000000000000000000000000000000..97a577df775849732402ac4809cc22b272e617d8
--- /dev/null
+++ b/remoting/ios/ui/scene_view.mm
@@ -0,0 +1,642 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "remoting/ios/ui/scene_view.h"
+
+#import "remoting/ios/utility.h"
+
+namespace {
+
+// TODO (aboone) Some of the layout is not yet set in stone, so variables have
+// been used to position and turn items on and off. Eventually these may be
+// stabilized and removed.
+
+// Scroll speed multiplier for swiping
+const static int kMouseSensitivity = 2.5;
+
+// Input Axis inversion
+// 1 for standard, -1 for inverted
+const static int kXAxisInversion = -1;
+const static int kYAxisInversion = -1;
+
+// Experimental value for bounding the maximum zoom ratio
+const static int kMaxZoomSize = 3;
+} // namespace
+
+@interface SceneView (Private)
+// Returns the number of pixels displayed per device pixel when the scaling is
+// such that the entire frame would fit perfectly in content. Note the ratios
+// are different for width and height, some people have multiple monitors, some
+// have 16:9 or 4:3 while iPad is always single screen, but different iOS
+// devices have different resolutions.
+- (CGPoint)pixelRatio;
+
+// Return the FrameSize in perspective of the CLIENT resolution
+- (webrtc::DesktopSize)frameSizeToScale:(float)scale;
+
+// When bounded on the top and right, this point is where the scene must be
+// positioned given a scene size
+- (webrtc::DesktopVector)getBoundsForSize:(const webrtc::DesktopSize&)size;
+
+// Converts a point in the the CLIENT resolution to a similar point in the HOST
+// resolution. Additionally, CLIENT resolution is expressed in float values
+// while HOST operates in integer values.
+- (BOOL)convertTouchPointToMousePoint:(CGPoint)touchPoint
+ targetPoint:(webrtc::DesktopVector&)desktopPoint;
+
+// Converts a point in the the HOST resolution to a similar point in the CLIENT
+// resolution. Additionally, CLIENT resolution is expressed in float values
+// while HOST operates in integer values.
+- (BOOL)convertMousePointToTouchPoint:(const webrtc::DesktopVector&)mousePoint
+ targetPoint:(CGPoint&)touchPoint;
+@end
+
+@implementation SceneView
+
+- (id)init {
+ self = [super init];
+ if (self) {
+
+ _frameSize = webrtc::DesktopSize(1, 1);
+ _contentSize = webrtc::DesktopSize(1, 1);
+ _mousePosition = webrtc::DesktopVector(0, 0);
+
+ _position = GLKVector3Make(0, 0, 1);
+ _margin.left = 0;
+ _margin.right = 0;
+ _margin.top = 0;
+ _margin.bottom = 0;
+ _anchored.left = false;
+ _anchored.right = false;
+ _anchored.top = false;
+ _anchored.bottom = false;
+ }
+ return self;
+}
+
+- (const GLKMatrix4&)projectionMatrix {
+ return _projectionMatrix;
+}
+
+- (const GLKMatrix4&)modelViewMatrix {
+ // Start by using the entire scene
+ _modelViewMatrix = GLKMatrix4Identity;
+
+ // Position scene according to any panning or bounds
+ _modelViewMatrix = GLKMatrix4Translate(_modelViewMatrix,
+ _position.x + _margin.left,
+ _position.y + _margin.bottom,
+ 0.0);
+
+ // Apply zoom
+ _modelViewMatrix = GLKMatrix4Scale(_modelViewMatrix,
+ _position.z / self.pixelRatio.x,
+ _position.z / self.pixelRatio.y,
+ 1.0);
+
+ // We are directly above the screen and looking down.
+ static const GLKMatrix4 viewMatrix = GLKMatrix4MakeLookAt(
+ 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); // center view
+
+ _modelViewMatrix = GLKMatrix4Multiply(viewMatrix, _modelViewMatrix);
+
+ return _modelViewMatrix;
+}
+
+- (const webrtc::DesktopSize&)contentSize {
+ return _contentSize;
+}
+
+- (void)setContentSize:(const CGSize&)size {
+
+ _contentSize.set(size.width, size.height);
+
+ _projectionMatrix = GLKMatrix4MakeOrtho(
+ 0.0, _contentSize.width(), 0.0, _contentSize.height(), 1.0, -1.0);
+
+ TexturedQuad newQuad;
+ newQuad.bl.geometryVertex = CGPointMake(0.0, 0.0);
+ newQuad.br.geometryVertex = CGPointMake(_contentSize.width(), 0.0);
+ newQuad.tl.geometryVertex = CGPointMake(0.0, _contentSize.height());
+ newQuad.tr.geometryVertex =
+ CGPointMake(_contentSize.width(), _contentSize.height());
+
+ newQuad.bl.textureVertex = CGPointMake(0.0, 1.0);
+ newQuad.br.textureVertex = CGPointMake(1.0, 1.0);
+ newQuad.tl.textureVertex = CGPointMake(0.0, 0.0);
+ newQuad.tr.textureVertex = CGPointMake(1.0, 0.0);
+
+ _glQuad = newQuad;
+}
+
+- (const webrtc::DesktopSize&)frameSize {
+ return _frameSize;
+}
+
+- (void)setFrameSize:(const webrtc::DesktopSize&)size {
+ DCHECK(size.width() > 0 && size.height() > 0);
+ // Don't do anything if the size has not changed.
+ if (_frameSize.equals(size))
+ return;
+
+ _frameSize.set(size.width(), size.height());
+
+ _position.x = 0;
+ _position.y = 0;
+
+ float verticalPixelScaleRatio =
+ (static_cast<float>(_contentSize.height() - _margin.top -
+ _margin.bottom) /
+ static_cast<float>(_frameSize.height())) /
+ _position.z;
+
+ // Anchored at the position (0,0)
+ _anchored.left = YES;
+ _anchored.right = NO;
+ _anchored.top = NO;
+ _anchored.bottom = YES;
+
+ [self panAndZoom:CGPointMake(0.0, 0.0) scaleBy:verticalPixelScaleRatio];
+
+ // Center the mouse on the CLIENT screen
+ webrtc::DesktopVector centerMouseLocation;
+ if ([self convertTouchPointToMousePoint:CGPointMake(_contentSize.width() / 2,
+ _contentSize.height() / 2)
+ targetPoint:centerMouseLocation]) {
+ _mousePosition.set(centerMouseLocation.x(), centerMouseLocation.y());
+ }
+
+#if DEBUG
+ NSLog(@"resized frame:%d:%d scale:%f",
+ _frameSize.width(),
+ _frameSize.height(),
+ _position.z);
+#endif // DEBUG
+}
+
+- (const webrtc::DesktopVector&)mousePosition {
+ return _mousePosition;
+}
+
+- (void)setPanVelocity:(const CGPoint&)delta {
+ _panVelocity.x = delta.x;
+ _panVelocity.y = delta.y;
+}
+
+- (void)setMarginsFromLeft:(int)left
+ right:(int)right
+ top:(int)top
+ bottom:(int)bottom {
+ _margin.left = left;
+ _margin.right = right;
+ _margin.top = top;
+ _margin.bottom = bottom;
+}
+
+- (void)draw {
+ glEnableVertexAttribArray(GLKVertexAttribPosition);
+ glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
+ glEnableVertexAttribArray(GLKVertexAttribTexCoord1);
+
+ // Define our scene space
+ glVertexAttribPointer(GLKVertexAttribPosition,
+ 2,
+ GL_FLOAT,
+ GL_FALSE,
+ sizeof(TexturedVertex),
+ &(_glQuad.bl.geometryVertex));
+ // Define the desktop plane
+ glVertexAttribPointer(GLKVertexAttribTexCoord0,
+ 2,
+ GL_FLOAT,
+ GL_FALSE,
+ sizeof(TexturedVertex),
+ &(_glQuad.bl.textureVertex));
+ // Define the cursor plane
+ glVertexAttribPointer(GLKVertexAttribTexCoord1,
+ 2,
+ GL_FLOAT,
+ GL_FALSE,
+ sizeof(TexturedVertex),
+ &(_glQuad.bl.textureVertex));
+
+ // Draw!
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+ [Utility logGLErrorCode:@"SceneView draw"];
+}
+
+- (CGPoint)pixelRatio {
+
+ CGPoint r = CGPointMake(static_cast<float>(_contentSize.width()) /
+ static_cast<float>(_frameSize.width()),
+ static_cast<float>(_contentSize.height()) /
+ static_cast<float>(_frameSize.height()));
+ return r;
+}
+
+- (webrtc::DesktopSize)frameSizeToScale:(float)scale {
+ return webrtc::DesktopSize(_frameSize.width() * scale,
+ _frameSize.height() * scale);
+}
+
+- (webrtc::DesktopVector)getBoundsForSize:(const webrtc::DesktopSize&)size {
+ webrtc::DesktopVector r(
+ _contentSize.width() - _margin.left - _margin.right - size.width(),
+ _contentSize.height() - _margin.bottom - _margin.top - size.height());
+
+ if (r.x() > 0) {
+ r.set((_contentSize.width() - size.width()) / 2, r.y());
+ }
+
+ if (r.y() > 0) {
+ r.set(r.x(), (_contentSize.height() - size.height()) / 2);
+ }
+
+ return r;
+}
+
+- (BOOL)containsTouchPoint:(CGPoint)point {
+ // Here frame is from the top-left corner, most other calculations are framed
+ // from the bottom left.
+ CGRect frame =
+ CGRectMake(_margin.left,
+ _margin.top,
+ _contentSize.width() - _margin.left - _margin.right,
+ _contentSize.height() - _margin.top - _margin.bottom);
+ return CGRectContainsPoint(frame, point);
+}
+
+- (BOOL)convertTouchPointToMousePoint:(CGPoint)touchPoint
+ targetPoint:(webrtc::DesktopVector&)mousePoint {
+ if (![self containsTouchPoint:touchPoint]) {
+ return NO;
+ }
+ // A touch location occurs in respect to the user's entire view surface.
+
+ // The GL Context is upside down from the User's perspective so flip it.
+ CGPoint glOrientedTouchPoint =
+ CGPointMake(touchPoint.x, _contentSize.height() - touchPoint.y);
+
+ // The GL surface generally is not at the same origination point as the touch,
+ // so translate by the scene's position.
+ CGPoint glOrientedPointInRespectToFrame =
+ CGPointMake(glOrientedTouchPoint.x - _position.x,
+ glOrientedTouchPoint.y - _position.y);
+
+ // The perspective exists in relative to the CLIENT resolution at 1:1, zoom
+ // our perspective so we are relative to the HOST at 1:1
+ CGPoint glOrientedPointInFrame =
+ CGPointMake(glOrientedPointInRespectToFrame.x / _position.z,
+ glOrientedPointInRespectToFrame.y / _position.z);
+
+ // Finally, flip the perspective back over to the Users, but this time in
+ // respect to the HOST desktop. Floor to ensure the result is always in
+ // frame.
+ CGPoint deskTopOrientedPointInFrame =
+ CGPointMake(floorf(glOrientedPointInFrame.x),
+ floorf(_frameSize.height() - glOrientedPointInFrame.y));
+
+ // Convert from float to integer
+ mousePoint.set(deskTopOrientedPointInFrame.x, deskTopOrientedPointInFrame.y);
+
+ return CGRectContainsPoint(
+ CGRectMake(0, 0, _frameSize.width(), _frameSize.height()),
+ deskTopOrientedPointInFrame);
+}
+
+- (BOOL)convertMousePointToTouchPoint:(const webrtc::DesktopVector&)mousePoint
+ targetPoint:(CGPoint&)touchPoint {
+ // A mouse point is in respect to the desktop frame.
+
+ // Flip the perspective back over to the Users, in
+ // respect to the HOST desktop.
+ CGPoint deskTopOrientedPointInFrame =
+ CGPointMake(mousePoint.x(), _frameSize.height() - mousePoint.y());
+
+ // The perspective exists in relative to the CLIENT resolution at 1:1, zoom
+ // our perspective so we are relative to the HOST at 1:1
+ CGPoint glOrientedPointInFrame =
+ CGPointMake(deskTopOrientedPointInFrame.x * _position.z,
+ deskTopOrientedPointInFrame.y * _position.z);
+
+ // The GL surface generally is not at the same origination point as the touch,
+ // so translate by the scene's position.
+ CGPoint glOrientedPointInRespectToFrame =
+ CGPointMake(glOrientedPointInFrame.x + _position.x,
+ glOrientedPointInFrame.y + _position.y);
+
+ // Convert from float to integer
+ touchPoint.x = floorf(glOrientedPointInRespectToFrame.x);
+ touchPoint.y = floorf(glOrientedPointInRespectToFrame.y);
+
+ return [self containsTouchPoint:touchPoint];
+}
+
+- (void)panAndZoom:(CGPoint)translation scaleBy:(float)ratio {
+ CGPoint ratios = [self pixelRatio];
+
+ // New Scaling factor bounded by a min and max
+ float resultScale = _position.z * ratio;
+ float scaleUpperBound = MAX(ratios.x, MAX(ratios.y, kMaxZoomSize));
+ float scaleLowerBound = MIN(ratios.x, ratios.y);
+
+ if (resultScale < scaleLowerBound) {
+ resultScale = scaleLowerBound;
+ } else if (resultScale > scaleUpperBound) {
+ resultScale = scaleUpperBound;
+ }
+
+ DCHECK(isnormal(resultScale) && resultScale > 0);
+
+ // The GL perspective is upside down in relation to the User's view, so flip
+ // the translation
+ translation.y = -translation.y;
+
+ // The constants here could be user options later.
+ translation.x =
+ translation.x * kXAxisInversion * (1 / (ratios.x * kMouseSensitivity));
+ translation.y =
+ translation.y * kYAxisInversion * (1 / (ratios.y * kMouseSensitivity));
+
+ CGPoint delta = CGPointMake(0, 0);
+ CGPoint scaleDelta = CGPointMake(0, 0);
+
+ webrtc::DesktopSize currentSize = [self frameSizeToScale:_position.z];
+
+ {
+ // Closure for this variable, so the variable is not available to the rest
+ // of this function
+ webrtc::DesktopVector currentBounds = [self getBoundsForSize:currentSize];
+ // There are rounding errors in the scope of this function, see the
+ // butterfly effect. In successive calls, the resulting position isn't
+ // always exactly the calculated position. If we know we are Anchored, then
+ // go ahead and reposition it to the values above.
+ if (_anchored.right) {
+ _position.x = currentBounds.x();
+ }
+
+ if (_anchored.top) {
+ _position.y = currentBounds.y();
+ }
+ }
+
+ if (_position.z != resultScale) {
+ // When scaling the scene, the origination of scaling is the mouse's
+ // location. But when the frame is anchored, adjust the origination to the
+ // anchor point.
+
+ CGPoint mousePositionInClientResolution;
+ [self convertMousePointToTouchPoint:_mousePosition
+ targetPoint:mousePositionInClientResolution];
+
+ // Prefer to zoom based on the left anchor when there is a choice
+ if (_anchored.left) {
+ mousePositionInClientResolution.x = 0;
+ } else if (_anchored.right) {
+ mousePositionInClientResolution.x = _contentSize.width();
+ }
+
+ // Prefer to zoom out from the top anchor when there is a choice
+ if (_anchored.top) {
+ mousePositionInClientResolution.y = _contentSize.height();
+ } else if (_anchored.bottom) {
+ mousePositionInClientResolution.y = 0;
+ }
+
+ scaleDelta.x -=
+ [SceneView positionDeltaFromScaling:ratio
+ position:_position.x
+ length:currentSize.width()
+ anchor:mousePositionInClientResolution.x];
+
+ scaleDelta.y -=
+ [SceneView positionDeltaFromScaling:ratio
+ position:_position.y
+ length:currentSize.height()
+ anchor:mousePositionInClientResolution.y];
+ }
+
+ delta.x = [SceneView
+ positionDeltaFromTranslation:translation.x
+ position:_position.x
+ freeSpace:_contentSize.width() - currentSize.width()
+ scaleingPositionDelta:scaleDelta.x
+ isAnchoredLow:_anchored.left
+ isAnchoredHigh:_anchored.right];
+
+ delta.y = [SceneView
+ positionDeltaFromTranslation:translation.y
+ position:_position.y
+ freeSpace:_contentSize.height() - currentSize.height()
+ scaleingPositionDelta:scaleDelta.y
+ isAnchoredLow:_anchored.bottom
+ isAnchoredHigh:_anchored.top];
+ {
+ // Closure for this variable, so the variable is not available to the rest
+ // of this function
+ webrtc::DesktopVector bounds =
+ [self getBoundsForSize:[self frameSizeToScale:resultScale]];
+
+ delta.x = [SceneView boundDeltaFromPosition:_position.x
+ delta:delta.x
+ lowerBound:bounds.x()
+ upperBound:0];
+
+ delta.y = [SceneView boundDeltaFromPosition:_position.y
+ delta:delta.y
+ lowerBound:bounds.y()
+ upperBound:0];
+ }
+
+ BOOL isLeftAndRightAnchored = _anchored.left && _anchored.right;
+ BOOL isTopAndBottomAnchored = _anchored.top && _anchored.bottom;
+
+ [self updateMousePositionAndAnchorsWithTranslation:translation
+ scale:resultScale];
+
+ // If both anchors were lost, then keep the one that is easier to predict
+ if (isLeftAndRightAnchored && !_anchored.left && !_anchored.right) {
+ delta.x = -_position.x;
+ _anchored.left = YES;
+ }
+
+ // If both anchors were lost, then keep the one that is easier to predict
+ if (isTopAndBottomAnchored && !_anchored.top && !_anchored.bottom) {
+ delta.y = -_position.y;
+ _anchored.bottom = YES;
+ }
+
+ // FINALLY, update the scene's position
+ _position.x += delta.x;
+ _position.y += delta.y;
+ _position.z = resultScale;
+}
+
+- (void)updateMousePositionAndAnchorsWithTranslation:(CGPoint)translation
+ scale:(float)scale {
+ webrtc::DesktopVector centerMouseLocation;
+ [self convertTouchPointToMousePoint:CGPointMake(_contentSize.width() / 2,
+ _contentSize.height() / 2)
+ targetPoint:centerMouseLocation];
+
+ webrtc::DesktopVector currentBounds =
+ [self getBoundsForSize:[self frameSizeToScale:_position.z]];
+ webrtc::DesktopVector nextBounds =
+ [self getBoundsForSize:[self frameSizeToScale:scale]];
+
+ webrtc::DesktopVector predictedMousePosition(
+ _mousePosition.x() - translation.x, _mousePosition.y() + translation.y);
+
+ _mousePosition.set(
+ [SceneView boundMouseGivenNextPosition:predictedMousePosition.x()
+ maxPosition:_frameSize.width()
+ centerPosition:centerMouseLocation.x()
+ isAnchoredLow:_anchored.left
+ isAnchoredHigh:_anchored.right],
+ [SceneView boundMouseGivenNextPosition:predictedMousePosition.y()
+ maxPosition:_frameSize.height()
+ centerPosition:centerMouseLocation.y()
+ isAnchoredLow:_anchored.top
+ isAnchoredHigh:_anchored.bottom]);
+
+ _panVelocity.x = [SceneView boundVelocity:_panVelocity.x
+ axisLength:_frameSize.width()
+ mousePosition:_mousePosition.x()];
+ _panVelocity.y = [SceneView boundVelocity:_panVelocity.y
+ axisLength:_frameSize.height()
+ mousePosition:_mousePosition.y()];
+
+ _anchored.left = (nextBounds.x() >= 0) ||
+ (_position.x == 0 &&
+ predictedMousePosition.x() <= centerMouseLocation.x());
+
+ _anchored.right =
+ (nextBounds.x() >= 0) ||
+ (_position.x == currentBounds.x() &&
+ predictedMousePosition.x() >= centerMouseLocation.x()) ||
+ (_mousePosition.x() == _frameSize.width() - 1 && !_anchored.left);
+
+ _anchored.bottom = (nextBounds.y() >= 0) ||
+ (_position.y == 0 &&
+ predictedMousePosition.y() >= centerMouseLocation.y());
+
+ _anchored.top =
+ (nextBounds.y() >= 0) ||
+ (_position.y == currentBounds.y() &&
+ predictedMousePosition.y() <= centerMouseLocation.y()) ||
+ (_mousePosition.y() == _frameSize.height() - 1 && !_anchored.bottom);
+}
+
++ (float)positionDeltaFromScaling:(float)ratio
+ position:(float)position
+ length:(float)length
+ anchor:(float)anchor {
+ float newSize = length * ratio;
+ float scaleXBy = fabs(position - anchor) / length;
+ float delta = (newSize - length) * scaleXBy;
+ return delta;
+}
+
++ (int)positionDeltaFromTranslation:(int)translation
+ position:(int)position
+ freeSpace:(int)freeSpace
+ scaleingPositionDelta:(int)scaleingPositionDelta
+ isAnchoredLow:(BOOL)isAnchoredLow
+ isAnchoredHigh:(BOOL)isAnchoredHigh {
+ if (isAnchoredLow && isAnchoredHigh) {
+ // center the view
+ return (freeSpace / 2) - position;
+ } else if (isAnchoredLow) {
+ return 0;
+ } else if (isAnchoredHigh) {
+ return scaleingPositionDelta;
+ } else {
+ return translation + scaleingPositionDelta;
+ }
+}
+
++ (int)boundDeltaFromPosition:(float)position
+ delta:(int)delta
+ lowerBound:(int)lowerBound
+ upperBound:(int)upperBound {
+ int result = position + delta;
+
+ if (lowerBound < upperBound) { // the view is larger than the bounds
+ if (result > upperBound) {
+ result = upperBound;
+ } else if (result < lowerBound) {
+ result = lowerBound;
+ }
+ } else {
+ // the view is smaller than the bounds so we'll always be at the lowerBound
+ result = lowerBound;
+ }
+ return result - position;
+}
+
++ (int)boundMouseGivenNextPosition:(int)nextPosition
+ maxPosition:(int)maxPosition
+ centerPosition:(int)centerPosition
+ isAnchoredLow:(BOOL)isAnchoredLow
+ isAnchoredHigh:(BOOL)isAnchoredHigh {
+ if (nextPosition < 0) {
+ return 0;
+ }
+ if (nextPosition > maxPosition - 1) {
+ return maxPosition - 1;
+ }
+
+ if ((isAnchoredLow && nextPosition <= centerPosition) ||
+ (isAnchoredHigh && nextPosition >= centerPosition)) {
+ return nextPosition;
+ }
+
+ return centerPosition;
+}
+
++ (float)boundVelocity:(float)velocity
+ axisLength:(int)axisLength
+ mousePosition:(int)mousePosition {
+ if (velocity != 0) {
+ if (mousePosition <= 0 || mousePosition >= (axisLength - 1)) {
+ return 0;
+ }
+ }
+
+ return velocity;
+}
+
+- (BOOL)tickPanVelocity {
+ BOOL inMotion = ((_panVelocity.x != 0.0) || (_panVelocity.y != 0.0));
+
+ if (inMotion) {
+
+ uint32_t divisor = 50 / _position.z;
+ float reducer = .95;
+
+ if (_panVelocity.x != 0.0 && ABS(_panVelocity.x) < divisor) {
+ _panVelocity = CGPointMake(0.0, _panVelocity.y);
+ }
+
+ if (_panVelocity.y != 0.0 && ABS(_panVelocity.y) < divisor) {
+ _panVelocity = CGPointMake(_panVelocity.x, 0.0);
+ }
+
+ [self panAndZoom:CGPointMake(_panVelocity.x / divisor,
+ _panVelocity.y / divisor)
+ scaleBy:1.0];
+
+ _panVelocity.x *= reducer;
+ _panVelocity.y *= reducer;
+ }
+
+ return inMotion;
+}
+
+@end
« no previous file with comments | « remoting/ios/ui/scene_view.h ('k') | remoting/ios/ui/scene_view_unittest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698