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 |