| OLD | NEW |
| (Empty) |
| 1 // Copyright 2014 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 #if !defined(__has_feature) || !__has_feature(objc_arc) | |
| 6 #error "This file requires ARC support." | |
| 7 #endif | |
| 8 | |
| 9 #import "remoting/ios/ui/scene_view.h" | |
| 10 | |
| 11 #import "remoting/ios/utility.h" | |
| 12 | |
| 13 namespace { | |
| 14 | |
| 15 // TODO (aboone) Some of the layout is not yet set in stone, so variables have | |
| 16 // been used to position and turn items on and off. Eventually these may be | |
| 17 // stabilized and removed. | |
| 18 | |
| 19 // Scroll speed multiplier for swiping | |
| 20 const static int kMouseSensitivity = 2.5; | |
| 21 | |
| 22 // Input Axis inversion | |
| 23 // 1 for standard, -1 for inverted | |
| 24 const static int kXAxisInversion = -1; | |
| 25 const static int kYAxisInversion = -1; | |
| 26 | |
| 27 // Experimental value for bounding the maximum zoom ratio | |
| 28 const static int kMaxZoomSize = 3; | |
| 29 } // namespace | |
| 30 | |
| 31 @interface SceneView (Private) | |
| 32 // Returns the number of pixels displayed per device pixel when the scaling is | |
| 33 // such that the entire frame would fit perfectly in content. Note the ratios | |
| 34 // are different for width and height, some people have multiple monitors, some | |
| 35 // have 16:9 or 4:3 while iPad is always single screen, but different iOS | |
| 36 // devices have different resolutions. | |
| 37 - (CGPoint)pixelRatio; | |
| 38 | |
| 39 // Return the FrameSize in perspective of the CLIENT resolution | |
| 40 - (webrtc::DesktopSize)frameSizeToScale:(float)scale; | |
| 41 | |
| 42 // When bounded on the top and right, this point is where the scene must be | |
| 43 // positioned given a scene size | |
| 44 - (webrtc::DesktopVector)getBoundsForSize:(const webrtc::DesktopSize&)size; | |
| 45 | |
| 46 // Converts a point in the the CLIENT resolution to a similar point in the HOST | |
| 47 // resolution. Additionally, CLIENT resolution is expressed in float values | |
| 48 // while HOST operates in integer values. | |
| 49 - (BOOL)convertTouchPointToMousePoint:(CGPoint)touchPoint | |
| 50 targetPoint:(webrtc::DesktopVector&)desktopPoint; | |
| 51 | |
| 52 // Converts a point in the the HOST resolution to a similar point in the CLIENT | |
| 53 // resolution. Additionally, CLIENT resolution is expressed in float values | |
| 54 // while HOST operates in integer values. | |
| 55 - (BOOL)convertMousePointToTouchPoint:(const webrtc::DesktopVector&)mousePoint | |
| 56 targetPoint:(CGPoint&)touchPoint; | |
| 57 @end | |
| 58 | |
| 59 @implementation SceneView | |
| 60 | |
| 61 - (id)init { | |
| 62 self = [super init]; | |
| 63 if (self) { | |
| 64 | |
| 65 _frameSize = webrtc::DesktopSize(1, 1); | |
| 66 _contentSize = webrtc::DesktopSize(1, 1); | |
| 67 _mousePosition = webrtc::DesktopVector(0, 0); | |
| 68 | |
| 69 _position = GLKVector3Make(0, 0, 1); | |
| 70 _margin.left = 0; | |
| 71 _margin.right = 0; | |
| 72 _margin.top = 0; | |
| 73 _margin.bottom = 0; | |
| 74 _anchored.left = false; | |
| 75 _anchored.right = false; | |
| 76 _anchored.top = false; | |
| 77 _anchored.bottom = false; | |
| 78 } | |
| 79 return self; | |
| 80 } | |
| 81 | |
| 82 - (const GLKMatrix4&)projectionMatrix { | |
| 83 return _projectionMatrix; | |
| 84 } | |
| 85 | |
| 86 - (const GLKMatrix4&)modelViewMatrix { | |
| 87 // Start by using the entire scene | |
| 88 _modelViewMatrix = GLKMatrix4Identity; | |
| 89 | |
| 90 // Position scene according to any panning or bounds | |
| 91 _modelViewMatrix = GLKMatrix4Translate(_modelViewMatrix, | |
| 92 _position.x + _margin.left, | |
| 93 _position.y + _margin.bottom, | |
| 94 0.0); | |
| 95 | |
| 96 // Apply zoom | |
| 97 _modelViewMatrix = GLKMatrix4Scale(_modelViewMatrix, | |
| 98 _position.z / self.pixelRatio.x, | |
| 99 _position.z / self.pixelRatio.y, | |
| 100 1.0); | |
| 101 | |
| 102 // We are directly above the screen and looking down. | |
| 103 static const GLKMatrix4 viewMatrix = GLKMatrix4MakeLookAt( | |
| 104 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); // center view | |
| 105 | |
| 106 _modelViewMatrix = GLKMatrix4Multiply(viewMatrix, _modelViewMatrix); | |
| 107 | |
| 108 return _modelViewMatrix; | |
| 109 } | |
| 110 | |
| 111 - (const webrtc::DesktopSize&)contentSize { | |
| 112 return _contentSize; | |
| 113 } | |
| 114 | |
| 115 - (void)setContentSize:(const CGSize&)size { | |
| 116 | |
| 117 _contentSize.set(size.width, size.height); | |
| 118 | |
| 119 _projectionMatrix = GLKMatrix4MakeOrtho( | |
| 120 0.0, _contentSize.width(), 0.0, _contentSize.height(), 1.0, -1.0); | |
| 121 | |
| 122 TexturedQuad newQuad; | |
| 123 newQuad.bl.geometryVertex = CGPointMake(0.0, 0.0); | |
| 124 newQuad.br.geometryVertex = CGPointMake(_contentSize.width(), 0.0); | |
| 125 newQuad.tl.geometryVertex = CGPointMake(0.0, _contentSize.height()); | |
| 126 newQuad.tr.geometryVertex = | |
| 127 CGPointMake(_contentSize.width(), _contentSize.height()); | |
| 128 | |
| 129 newQuad.bl.textureVertex = CGPointMake(0.0, 1.0); | |
| 130 newQuad.br.textureVertex = CGPointMake(1.0, 1.0); | |
| 131 newQuad.tl.textureVertex = CGPointMake(0.0, 0.0); | |
| 132 newQuad.tr.textureVertex = CGPointMake(1.0, 0.0); | |
| 133 | |
| 134 _glQuad = newQuad; | |
| 135 } | |
| 136 | |
| 137 - (const webrtc::DesktopSize&)frameSize { | |
| 138 return _frameSize; | |
| 139 } | |
| 140 | |
| 141 - (void)setFrameSize:(const webrtc::DesktopSize&)size { | |
| 142 DCHECK(size.width() > 0 && size.height() > 0); | |
| 143 // Don't do anything if the size has not changed. | |
| 144 if (_frameSize.equals(size)) | |
| 145 return; | |
| 146 | |
| 147 _frameSize.set(size.width(), size.height()); | |
| 148 | |
| 149 _position.x = 0; | |
| 150 _position.y = 0; | |
| 151 | |
| 152 float verticalPixelScaleRatio = | |
| 153 (static_cast<float>(_contentSize.height() - _margin.top - | |
| 154 _margin.bottom) / | |
| 155 static_cast<float>(_frameSize.height())) / | |
| 156 _position.z; | |
| 157 | |
| 158 // Anchored at the position (0,0) | |
| 159 _anchored.left = YES; | |
| 160 _anchored.right = NO; | |
| 161 _anchored.top = NO; | |
| 162 _anchored.bottom = YES; | |
| 163 | |
| 164 [self panAndZoom:CGPointMake(0.0, 0.0) scaleBy:verticalPixelScaleRatio]; | |
| 165 | |
| 166 // Center the mouse on the CLIENT screen | |
| 167 webrtc::DesktopVector centerMouseLocation; | |
| 168 if ([self convertTouchPointToMousePoint:CGPointMake(_contentSize.width() / 2, | |
| 169 _contentSize.height() / 2) | |
| 170 targetPoint:centerMouseLocation]) { | |
| 171 _mousePosition.set(centerMouseLocation.x(), centerMouseLocation.y()); | |
| 172 } | |
| 173 | |
| 174 #if DEBUG | |
| 175 NSLog(@"resized frame:%d:%d scale:%f", | |
| 176 _frameSize.width(), | |
| 177 _frameSize.height(), | |
| 178 _position.z); | |
| 179 #endif // DEBUG | |
| 180 } | |
| 181 | |
| 182 - (const webrtc::DesktopVector&)mousePosition { | |
| 183 return _mousePosition; | |
| 184 } | |
| 185 | |
| 186 - (void)setPanVelocity:(const CGPoint&)delta { | |
| 187 _panVelocity.x = delta.x; | |
| 188 _panVelocity.y = delta.y; | |
| 189 } | |
| 190 | |
| 191 - (void)setMarginsFromLeft:(int)left | |
| 192 right:(int)right | |
| 193 top:(int)top | |
| 194 bottom:(int)bottom { | |
| 195 _margin.left = left; | |
| 196 _margin.right = right; | |
| 197 _margin.top = top; | |
| 198 _margin.bottom = bottom; | |
| 199 } | |
| 200 | |
| 201 - (void)draw { | |
| 202 glEnableVertexAttribArray(GLKVertexAttribPosition); | |
| 203 glEnableVertexAttribArray(GLKVertexAttribTexCoord0); | |
| 204 glEnableVertexAttribArray(GLKVertexAttribTexCoord1); | |
| 205 | |
| 206 // Define our scene space | |
| 207 glVertexAttribPointer(GLKVertexAttribPosition, | |
| 208 2, | |
| 209 GL_FLOAT, | |
| 210 GL_FALSE, | |
| 211 sizeof(TexturedVertex), | |
| 212 &(_glQuad.bl.geometryVertex)); | |
| 213 // Define the desktop plane | |
| 214 glVertexAttribPointer(GLKVertexAttribTexCoord0, | |
| 215 2, | |
| 216 GL_FLOAT, | |
| 217 GL_FALSE, | |
| 218 sizeof(TexturedVertex), | |
| 219 &(_glQuad.bl.textureVertex)); | |
| 220 // Define the cursor plane | |
| 221 glVertexAttribPointer(GLKVertexAttribTexCoord1, | |
| 222 2, | |
| 223 GL_FLOAT, | |
| 224 GL_FALSE, | |
| 225 sizeof(TexturedVertex), | |
| 226 &(_glQuad.bl.textureVertex)); | |
| 227 | |
| 228 // Draw! | |
| 229 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); | |
| 230 | |
| 231 [Utility logGLErrorCode:@"SceneView draw"]; | |
| 232 } | |
| 233 | |
| 234 - (CGPoint)pixelRatio { | |
| 235 | |
| 236 CGPoint r = CGPointMake(static_cast<float>(_contentSize.width()) / | |
| 237 static_cast<float>(_frameSize.width()), | |
| 238 static_cast<float>(_contentSize.height()) / | |
| 239 static_cast<float>(_frameSize.height())); | |
| 240 return r; | |
| 241 } | |
| 242 | |
| 243 - (webrtc::DesktopSize)frameSizeToScale:(float)scale { | |
| 244 return webrtc::DesktopSize(_frameSize.width() * scale, | |
| 245 _frameSize.height() * scale); | |
| 246 } | |
| 247 | |
| 248 - (webrtc::DesktopVector)getBoundsForSize:(const webrtc::DesktopSize&)size { | |
| 249 webrtc::DesktopVector r( | |
| 250 _contentSize.width() - _margin.left - _margin.right - size.width(), | |
| 251 _contentSize.height() - _margin.bottom - _margin.top - size.height()); | |
| 252 | |
| 253 if (r.x() > 0) { | |
| 254 r.set((_contentSize.width() - size.width()) / 2, r.y()); | |
| 255 } | |
| 256 | |
| 257 if (r.y() > 0) { | |
| 258 r.set(r.x(), (_contentSize.height() - size.height()) / 2); | |
| 259 } | |
| 260 | |
| 261 return r; | |
| 262 } | |
| 263 | |
| 264 - (BOOL)containsTouchPoint:(CGPoint)point { | |
| 265 // Here frame is from the top-left corner, most other calculations are framed | |
| 266 // from the bottom left. | |
| 267 CGRect frame = | |
| 268 CGRectMake(_margin.left, | |
| 269 _margin.top, | |
| 270 _contentSize.width() - _margin.left - _margin.right, | |
| 271 _contentSize.height() - _margin.top - _margin.bottom); | |
| 272 return CGRectContainsPoint(frame, point); | |
| 273 } | |
| 274 | |
| 275 - (BOOL)convertTouchPointToMousePoint:(CGPoint)touchPoint | |
| 276 targetPoint:(webrtc::DesktopVector&)mousePoint { | |
| 277 if (![self containsTouchPoint:touchPoint]) { | |
| 278 return NO; | |
| 279 } | |
| 280 // A touch location occurs in respect to the user's entire view surface. | |
| 281 | |
| 282 // The GL Context is upside down from the User's perspective so flip it. | |
| 283 CGPoint glOrientedTouchPoint = | |
| 284 CGPointMake(touchPoint.x, _contentSize.height() - touchPoint.y); | |
| 285 | |
| 286 // The GL surface generally is not at the same origination point as the touch, | |
| 287 // so translate by the scene's position. | |
| 288 CGPoint glOrientedPointInRespectToFrame = | |
| 289 CGPointMake(glOrientedTouchPoint.x - _position.x, | |
| 290 glOrientedTouchPoint.y - _position.y); | |
| 291 | |
| 292 // The perspective exists in relative to the CLIENT resolution at 1:1, zoom | |
| 293 // our perspective so we are relative to the HOST at 1:1 | |
| 294 CGPoint glOrientedPointInFrame = | |
| 295 CGPointMake(glOrientedPointInRespectToFrame.x / _position.z, | |
| 296 glOrientedPointInRespectToFrame.y / _position.z); | |
| 297 | |
| 298 // Finally, flip the perspective back over to the Users, but this time in | |
| 299 // respect to the HOST desktop. Floor to ensure the result is always in | |
| 300 // frame. | |
| 301 CGPoint deskTopOrientedPointInFrame = | |
| 302 CGPointMake(floorf(glOrientedPointInFrame.x), | |
| 303 floorf(_frameSize.height() - glOrientedPointInFrame.y)); | |
| 304 | |
| 305 // Convert from float to integer | |
| 306 mousePoint.set(deskTopOrientedPointInFrame.x, deskTopOrientedPointInFrame.y); | |
| 307 | |
| 308 return CGRectContainsPoint( | |
| 309 CGRectMake(0, 0, _frameSize.width(), _frameSize.height()), | |
| 310 deskTopOrientedPointInFrame); | |
| 311 } | |
| 312 | |
| 313 - (BOOL)convertMousePointToTouchPoint:(const webrtc::DesktopVector&)mousePoint | |
| 314 targetPoint:(CGPoint&)touchPoint { | |
| 315 // A mouse point is in respect to the desktop frame. | |
| 316 | |
| 317 // Flip the perspective back over to the Users, in | |
| 318 // respect to the HOST desktop. | |
| 319 CGPoint deskTopOrientedPointInFrame = | |
| 320 CGPointMake(mousePoint.x(), _frameSize.height() - mousePoint.y()); | |
| 321 | |
| 322 // The perspective exists in relative to the CLIENT resolution at 1:1, zoom | |
| 323 // our perspective so we are relative to the HOST at 1:1 | |
| 324 CGPoint glOrientedPointInFrame = | |
| 325 CGPointMake(deskTopOrientedPointInFrame.x * _position.z, | |
| 326 deskTopOrientedPointInFrame.y * _position.z); | |
| 327 | |
| 328 // The GL surface generally is not at the same origination point as the touch, | |
| 329 // so translate by the scene's position. | |
| 330 CGPoint glOrientedPointInRespectToFrame = | |
| 331 CGPointMake(glOrientedPointInFrame.x + _position.x, | |
| 332 glOrientedPointInFrame.y + _position.y); | |
| 333 | |
| 334 // Convert from float to integer | |
| 335 touchPoint.x = floorf(glOrientedPointInRespectToFrame.x); | |
| 336 touchPoint.y = floorf(glOrientedPointInRespectToFrame.y); | |
| 337 | |
| 338 return [self containsTouchPoint:touchPoint]; | |
| 339 } | |
| 340 | |
| 341 - (void)panAndZoom:(CGPoint)translation scaleBy:(float)ratio { | |
| 342 CGPoint ratios = [self pixelRatio]; | |
| 343 | |
| 344 // New Scaling factor bounded by a min and max | |
| 345 float resultScale = _position.z * ratio; | |
| 346 float scaleUpperBound = MAX(ratios.x, MAX(ratios.y, kMaxZoomSize)); | |
| 347 float scaleLowerBound = MIN(ratios.x, ratios.y); | |
| 348 | |
| 349 if (resultScale < scaleLowerBound) { | |
| 350 resultScale = scaleLowerBound; | |
| 351 } else if (resultScale > scaleUpperBound) { | |
| 352 resultScale = scaleUpperBound; | |
| 353 } | |
| 354 | |
| 355 DCHECK(isnormal(resultScale) && resultScale > 0); | |
| 356 | |
| 357 // The GL perspective is upside down in relation to the User's view, so flip | |
| 358 // the translation | |
| 359 translation.y = -translation.y; | |
| 360 | |
| 361 // The constants here could be user options later. | |
| 362 translation.x = | |
| 363 translation.x * kXAxisInversion * (1 / (ratios.x * kMouseSensitivity)); | |
| 364 translation.y = | |
| 365 translation.y * kYAxisInversion * (1 / (ratios.y * kMouseSensitivity)); | |
| 366 | |
| 367 CGPoint delta = CGPointMake(0, 0); | |
| 368 CGPoint scaleDelta = CGPointMake(0, 0); | |
| 369 | |
| 370 webrtc::DesktopSize currentSize = [self frameSizeToScale:_position.z]; | |
| 371 | |
| 372 { | |
| 373 // Closure for this variable, so the variable is not available to the rest | |
| 374 // of this function | |
| 375 webrtc::DesktopVector currentBounds = [self getBoundsForSize:currentSize]; | |
| 376 // There are rounding errors in the scope of this function, see the | |
| 377 // butterfly effect. In successive calls, the resulting position isn't | |
| 378 // always exactly the calculated position. If we know we are Anchored, then | |
| 379 // go ahead and reposition it to the values above. | |
| 380 if (_anchored.right) { | |
| 381 _position.x = currentBounds.x(); | |
| 382 } | |
| 383 | |
| 384 if (_anchored.top) { | |
| 385 _position.y = currentBounds.y(); | |
| 386 } | |
| 387 } | |
| 388 | |
| 389 if (_position.z != resultScale) { | |
| 390 // When scaling the scene, the origination of scaling is the mouse's | |
| 391 // location. But when the frame is anchored, adjust the origination to the | |
| 392 // anchor point. | |
| 393 | |
| 394 CGPoint mousePositionInClientResolution; | |
| 395 [self convertMousePointToTouchPoint:_mousePosition | |
| 396 targetPoint:mousePositionInClientResolution]; | |
| 397 | |
| 398 // Prefer to zoom based on the left anchor when there is a choice | |
| 399 if (_anchored.left) { | |
| 400 mousePositionInClientResolution.x = 0; | |
| 401 } else if (_anchored.right) { | |
| 402 mousePositionInClientResolution.x = _contentSize.width(); | |
| 403 } | |
| 404 | |
| 405 // Prefer to zoom out from the top anchor when there is a choice | |
| 406 if (_anchored.top) { | |
| 407 mousePositionInClientResolution.y = _contentSize.height(); | |
| 408 } else if (_anchored.bottom) { | |
| 409 mousePositionInClientResolution.y = 0; | |
| 410 } | |
| 411 | |
| 412 scaleDelta.x -= | |
| 413 [SceneView positionDeltaFromScaling:ratio | |
| 414 position:_position.x | |
| 415 length:currentSize.width() | |
| 416 anchor:mousePositionInClientResolution.x]; | |
| 417 | |
| 418 scaleDelta.y -= | |
| 419 [SceneView positionDeltaFromScaling:ratio | |
| 420 position:_position.y | |
| 421 length:currentSize.height() | |
| 422 anchor:mousePositionInClientResolution.y]; | |
| 423 } | |
| 424 | |
| 425 delta.x = [SceneView | |
| 426 positionDeltaFromTranslation:translation.x | |
| 427 position:_position.x | |
| 428 freeSpace:_contentSize.width() - currentSize.width() | |
| 429 scaleingPositionDelta:scaleDelta.x | |
| 430 isAnchoredLow:_anchored.left | |
| 431 isAnchoredHigh:_anchored.right]; | |
| 432 | |
| 433 delta.y = [SceneView | |
| 434 positionDeltaFromTranslation:translation.y | |
| 435 position:_position.y | |
| 436 freeSpace:_contentSize.height() - currentSize.height() | |
| 437 scaleingPositionDelta:scaleDelta.y | |
| 438 isAnchoredLow:_anchored.bottom | |
| 439 isAnchoredHigh:_anchored.top]; | |
| 440 { | |
| 441 // Closure for this variable, so the variable is not available to the rest | |
| 442 // of this function | |
| 443 webrtc::DesktopVector bounds = | |
| 444 [self getBoundsForSize:[self frameSizeToScale:resultScale]]; | |
| 445 | |
| 446 delta.x = [SceneView boundDeltaFromPosition:_position.x | |
| 447 delta:delta.x | |
| 448 lowerBound:bounds.x() | |
| 449 upperBound:0]; | |
| 450 | |
| 451 delta.y = [SceneView boundDeltaFromPosition:_position.y | |
| 452 delta:delta.y | |
| 453 lowerBound:bounds.y() | |
| 454 upperBound:0]; | |
| 455 } | |
| 456 | |
| 457 BOOL isLeftAndRightAnchored = _anchored.left && _anchored.right; | |
| 458 BOOL isTopAndBottomAnchored = _anchored.top && _anchored.bottom; | |
| 459 | |
| 460 [self updateMousePositionAndAnchorsWithTranslation:translation | |
| 461 scale:resultScale]; | |
| 462 | |
| 463 // If both anchors were lost, then keep the one that is easier to predict | |
| 464 if (isLeftAndRightAnchored && !_anchored.left && !_anchored.right) { | |
| 465 delta.x = -_position.x; | |
| 466 _anchored.left = YES; | |
| 467 } | |
| 468 | |
| 469 // If both anchors were lost, then keep the one that is easier to predict | |
| 470 if (isTopAndBottomAnchored && !_anchored.top && !_anchored.bottom) { | |
| 471 delta.y = -_position.y; | |
| 472 _anchored.bottom = YES; | |
| 473 } | |
| 474 | |
| 475 // FINALLY, update the scene's position | |
| 476 _position.x += delta.x; | |
| 477 _position.y += delta.y; | |
| 478 _position.z = resultScale; | |
| 479 } | |
| 480 | |
| 481 - (void)updateMousePositionAndAnchorsWithTranslation:(CGPoint)translation | |
| 482 scale:(float)scale { | |
| 483 webrtc::DesktopVector centerMouseLocation; | |
| 484 [self convertTouchPointToMousePoint:CGPointMake(_contentSize.width() / 2, | |
| 485 _contentSize.height() / 2) | |
| 486 targetPoint:centerMouseLocation]; | |
| 487 | |
| 488 webrtc::DesktopVector currentBounds = | |
| 489 [self getBoundsForSize:[self frameSizeToScale:_position.z]]; | |
| 490 webrtc::DesktopVector nextBounds = | |
| 491 [self getBoundsForSize:[self frameSizeToScale:scale]]; | |
| 492 | |
| 493 webrtc::DesktopVector predictedMousePosition( | |
| 494 _mousePosition.x() - translation.x, _mousePosition.y() + translation.y); | |
| 495 | |
| 496 _mousePosition.set( | |
| 497 [SceneView boundMouseGivenNextPosition:predictedMousePosition.x() | |
| 498 maxPosition:_frameSize.width() | |
| 499 centerPosition:centerMouseLocation.x() | |
| 500 isAnchoredLow:_anchored.left | |
| 501 isAnchoredHigh:_anchored.right], | |
| 502 [SceneView boundMouseGivenNextPosition:predictedMousePosition.y() | |
| 503 maxPosition:_frameSize.height() | |
| 504 centerPosition:centerMouseLocation.y() | |
| 505 isAnchoredLow:_anchored.top | |
| 506 isAnchoredHigh:_anchored.bottom]); | |
| 507 | |
| 508 _panVelocity.x = [SceneView boundVelocity:_panVelocity.x | |
| 509 axisLength:_frameSize.width() | |
| 510 mousePosition:_mousePosition.x()]; | |
| 511 _panVelocity.y = [SceneView boundVelocity:_panVelocity.y | |
| 512 axisLength:_frameSize.height() | |
| 513 mousePosition:_mousePosition.y()]; | |
| 514 | |
| 515 _anchored.left = (nextBounds.x() >= 0) || | |
| 516 (_position.x == 0 && | |
| 517 predictedMousePosition.x() <= centerMouseLocation.x()); | |
| 518 | |
| 519 _anchored.right = | |
| 520 (nextBounds.x() >= 0) || | |
| 521 (_position.x == currentBounds.x() && | |
| 522 predictedMousePosition.x() >= centerMouseLocation.x()) || | |
| 523 (_mousePosition.x() == _frameSize.width() - 1 && !_anchored.left); | |
| 524 | |
| 525 _anchored.bottom = (nextBounds.y() >= 0) || | |
| 526 (_position.y == 0 && | |
| 527 predictedMousePosition.y() >= centerMouseLocation.y()); | |
| 528 | |
| 529 _anchored.top = | |
| 530 (nextBounds.y() >= 0) || | |
| 531 (_position.y == currentBounds.y() && | |
| 532 predictedMousePosition.y() <= centerMouseLocation.y()) || | |
| 533 (_mousePosition.y() == _frameSize.height() - 1 && !_anchored.bottom); | |
| 534 } | |
| 535 | |
| 536 + (float)positionDeltaFromScaling:(float)ratio | |
| 537 position:(float)position | |
| 538 length:(float)length | |
| 539 anchor:(float)anchor { | |
| 540 float newSize = length * ratio; | |
| 541 float scaleXBy = fabs(position - anchor) / length; | |
| 542 float delta = (newSize - length) * scaleXBy; | |
| 543 return delta; | |
| 544 } | |
| 545 | |
| 546 + (int)positionDeltaFromTranslation:(int)translation | |
| 547 position:(int)position | |
| 548 freeSpace:(int)freeSpace | |
| 549 scaleingPositionDelta:(int)scaleingPositionDelta | |
| 550 isAnchoredLow:(BOOL)isAnchoredLow | |
| 551 isAnchoredHigh:(BOOL)isAnchoredHigh { | |
| 552 if (isAnchoredLow && isAnchoredHigh) { | |
| 553 // center the view | |
| 554 return (freeSpace / 2) - position; | |
| 555 } else if (isAnchoredLow) { | |
| 556 return 0; | |
| 557 } else if (isAnchoredHigh) { | |
| 558 return scaleingPositionDelta; | |
| 559 } else { | |
| 560 return translation + scaleingPositionDelta; | |
| 561 } | |
| 562 } | |
| 563 | |
| 564 + (int)boundDeltaFromPosition:(float)position | |
| 565 delta:(int)delta | |
| 566 lowerBound:(int)lowerBound | |
| 567 upperBound:(int)upperBound { | |
| 568 int result = position + delta; | |
| 569 | |
| 570 if (lowerBound < upperBound) { // the view is larger than the bounds | |
| 571 if (result > upperBound) { | |
| 572 result = upperBound; | |
| 573 } else if (result < lowerBound) { | |
| 574 result = lowerBound; | |
| 575 } | |
| 576 } else { | |
| 577 // the view is smaller than the bounds so we'll always be at the lowerBound | |
| 578 result = lowerBound; | |
| 579 } | |
| 580 return result - position; | |
| 581 } | |
| 582 | |
| 583 + (int)boundMouseGivenNextPosition:(int)nextPosition | |
| 584 maxPosition:(int)maxPosition | |
| 585 centerPosition:(int)centerPosition | |
| 586 isAnchoredLow:(BOOL)isAnchoredLow | |
| 587 isAnchoredHigh:(BOOL)isAnchoredHigh { | |
| 588 if (nextPosition < 0) { | |
| 589 return 0; | |
| 590 } | |
| 591 if (nextPosition > maxPosition - 1) { | |
| 592 return maxPosition - 1; | |
| 593 } | |
| 594 | |
| 595 if ((isAnchoredLow && nextPosition <= centerPosition) || | |
| 596 (isAnchoredHigh && nextPosition >= centerPosition)) { | |
| 597 return nextPosition; | |
| 598 } | |
| 599 | |
| 600 return centerPosition; | |
| 601 } | |
| 602 | |
| 603 + (float)boundVelocity:(float)velocity | |
| 604 axisLength:(int)axisLength | |
| 605 mousePosition:(int)mousePosition { | |
| 606 if (velocity != 0) { | |
| 607 if (mousePosition <= 0 || mousePosition >= (axisLength - 1)) { | |
| 608 return 0; | |
| 609 } | |
| 610 } | |
| 611 | |
| 612 return velocity; | |
| 613 } | |
| 614 | |
| 615 - (BOOL)tickPanVelocity { | |
| 616 BOOL inMotion = ((_panVelocity.x != 0.0) || (_panVelocity.y != 0.0)); | |
| 617 | |
| 618 if (inMotion) { | |
| 619 | |
| 620 uint32_t divisor = 50 / _position.z; | |
| 621 float reducer = .95; | |
| 622 | |
| 623 if (_panVelocity.x != 0.0 && ABS(_panVelocity.x) < divisor) { | |
| 624 _panVelocity = CGPointMake(0.0, _panVelocity.y); | |
| 625 } | |
| 626 | |
| 627 if (_panVelocity.y != 0.0 && ABS(_panVelocity.y) < divisor) { | |
| 628 _panVelocity = CGPointMake(_panVelocity.x, 0.0); | |
| 629 } | |
| 630 | |
| 631 [self panAndZoom:CGPointMake(_panVelocity.x / divisor, | |
| 632 _panVelocity.y / divisor) | |
| 633 scaleBy:1.0]; | |
| 634 | |
| 635 _panVelocity.x *= reducer; | |
| 636 _panVelocity.y *= reducer; | |
| 637 } | |
| 638 | |
| 639 return inMotion; | |
| 640 } | |
| 641 | |
| 642 @end | |
| OLD | NEW |