| 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/host_view_controller.h" |
| 10 |
| 11 #include <OpenGLES/ES2/gl.h> |
| 12 |
| 13 #import "remoting/ios/data_store.h" |
| 14 |
| 15 namespace { |
| 16 |
| 17 // TODO (aboone) Some of the layout is not yet set in stone, so variables have |
| 18 // been used to possition and turn items on and off. Evantually these should be |
| 19 // stabilized and removed. |
| 20 |
| 21 // Scrool speed multiplier for swiping |
| 22 const static int kMouseSensitivity = 2.5; |
| 23 |
| 24 // Scrool speed multiplier for mouse wheel |
| 25 const static int kMouseWheelSensitivity = 20; |
| 26 |
| 27 // Input Axis inversion |
| 28 // 1 for standard, -1 for inverted |
| 29 const static int kXAxisInversion = -1; |
| 30 const static int kYAxisInversion = -1; |
| 31 |
| 32 // Area the navigation bar consumes when visible in pixels |
| 33 const static int kTopMargin = 19; |
| 34 // Area the tool bar consumes when visible in pixels |
| 35 const static int kBottomMargin = 0; |
| 36 // Experimental value for bounding the maximum zoom ratio |
| 37 const static int kMaxZoomSize = 3; |
| 38 // Distance between Button in the Tool bar in pixels |
| 39 const static int kButtonSpacer = 15; |
| 40 |
| 41 #ifdef DEBUG |
| 42 // Some basic statistics. |
| 43 // The number of canvas updates received from the network. |
| 44 static uint32_t _applyCount = 0; |
| 45 // The number of frames drawn to the GL Context |
| 46 static uint32_t _frameCount = 0; |
| 47 // The number of frames that also required canvas updates to be drawn to the GL |
| 48 // Context. |
| 49 static uint32_t _drawCount = 0; |
| 50 #endif // DEBUG |
| 51 } // namespace |
| 52 |
| 53 @interface HostViewController (Private) |
| 54 - (void)setupGL; |
| 55 - (void)tearDownGL; |
| 56 - (void)bindTextureForIOS:(GLuint)glName; |
| 57 - (void)checkConnectStatus; |
| 58 - (void)doConnectToHostWithPin:(NSString*)hostPin |
| 59 createPairing:(BOOL)createPair; |
| 60 - (BOOL)convertTouchPointToMousePoint:(CGPoint)touchPoint |
| 61 targetPoint:(webrtc::DesktopVector&)desktopPoint; |
| 62 - (BOOL)convertMousePointToTouchPoint:(const webrtc::DesktopVector&)mousePoint |
| 63 targetPoint:(CGPoint&)touchPoint; |
| 64 - (void)logGLErrorCode:(NSString*)funcName; |
| 65 - (void)setFrameForControls; |
| 66 - (CGRect)getViewBounds; |
| 67 - (void)updateContentSize; |
| 68 - (void)updatePanVelocityShouldCancel:(bool)canceled; |
| 69 - (void)orientationChanged:(NSNotification*)note; |
| 70 - (webrtc::DesktopSize)getCurrentSize; |
| 71 - (void)panAndZoom:(CGPoint)translation scaleBy:(float)ratio; |
| 72 + (float)calculateScaleingDelta:(float)scale |
| 73 size:(float)size |
| 74 position:(float)position |
| 75 anchor:(float)anchor; |
| 76 + (int)calculatePositionDelta:(int)position |
| 77 length:(int)length |
| 78 translation:(int)translation |
| 79 scaleDelta:(int)scaleDelta |
| 80 isAnchoredLow:(BOOL)isAnchoredLow |
| 81 isAnchoredHigh:(BOOL)isAnchoredHigh; |
| 82 + (int)applyBoundsToFrameAxis:(float)position |
| 83 delta:(int)delta |
| 84 lowerBound:(int)lowerBound |
| 85 upperBound:(int)upperBound; |
| 86 + (int)calculateMousePosition:(int)nextPosition |
| 87 maxPosition:(int)maxPosition |
| 88 centerPosition:(int)centerPosition |
| 89 isAnchoredLow:(BOOL)isAnchoredLow |
| 90 isAnchoredHigh:(BOOL)isAnchoredHigh; |
| 91 - (void)cancelVelocityWhenAtEdge; |
| 92 - (void)updateMousePositionAndAnchorsWithBounds: |
| 93 (const webrtc::DesktopVector&)bounds |
| 94 translation:(CGPoint)translation; |
| 95 - (BOOL)isPointInScene:(CGPoint)point; |
| 96 - (void)showToolbar:(BOOL)visible; |
| 97 @end |
| 98 |
| 99 @implementation HostViewController |
| 100 |
| 101 // Override UIViewController |
| 102 - (void)viewDidLoad { |
| 103 [super viewDidLoad]; |
| 104 |
| 105 _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; |
| 106 |
| 107 DCHECK(_context); |
| 108 |
| 109 GLKView* view = static_cast<GLKView*>(self.view); |
| 110 view.context = _context; |
| 111 |
| 112 [_keyEntryView setDelegate:self]; |
| 113 |
| 114 _controller = [[ClientController alloc] init]; |
| 115 |
| 116 _updateDisplayTimer = |
| 117 [NSTimer scheduledTimerWithTimeInterval:0.4 |
| 118 target:self |
| 119 selector:@selector(checkConnectStatus) |
| 120 userInfo:nil |
| 121 repeats:NO]; |
| 122 _mousePosition.set(0, 0); |
| 123 _glBufferLock = [[NSLock alloc] init]; |
| 124 _glCursorLock = [[NSLock alloc] init]; |
| 125 _needCursorRedraw = NO; |
| 126 _cursorDrawnToGL = webrtc::DesktopRect::MakeXYWH(0, 0, 0, 0); |
| 127 _sizeChanged = YES; // rgba |
| 128 _frameSize.set(1, 1); |
| 129 _scenePosition = GLKVector3Make(0, 0, 1); |
| 130 |
| 131 [self updatePanVelocityShouldCancel:YES]; |
| 132 |
| 133 // [self updateContectSize] is implicit in this call, and required at this |
| 134 // point |
| 135 [self showToolbar:NO]; |
| 136 |
| 137 [self setupGL]; |
| 138 |
| 139 [_singleTapRecognizer requireGestureRecognizerToFail:_twoFingerTapRecognizer]; |
| 140 [_twoFingerTapRecognizer |
| 141 requireGestureRecognizerToFail:_threeFingerTapRecognizer]; |
| 142 //[_pinchRecognizer requireGestureRecognizerToFail:_twoFingerTapRecognizer]; |
| 143 [_panRecognizer requireGestureRecognizerToFail:_singleTapRecognizer]; |
| 144 [_threeFingerPanRecognizer |
| 145 requireGestureRecognizerToFail:_threeFingerTapRecognizer]; |
| 146 //[_pinchRecognizer requireGestureRecognizerToFail:_threeFingerPanRecognizer]; |
| 147 |
| 148 // Subscribe to changes in orientation |
| 149 [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; |
| 150 [[NSNotificationCenter defaultCenter] |
| 151 addObserver:self |
| 152 selector:@selector(orientationChanged:) |
| 153 name:UIDeviceOrientationDidChangeNotification |
| 154 object:[UIDevice currentDevice]]; |
| 155 |
| 156 [self.navigationController setNavigationBarHidden:YES animated:YES]; |
| 157 |
| 158 // We impliment UIBarPositioningDelegate to handle this class's delegate |
| 159 [_viewToolbar setDelegate:self]; |
| 160 // Causes |_viewToolbar| to call |positionForBar| via it's delegate where we |
| 161 // can inform it of it's relative position |
| 162 [self setNeedsStatusBarAppearanceUpdate]; |
| 163 } |
| 164 |
| 165 - (void)setupGL { |
| 166 [EAGLContext setCurrentContext:_context]; |
| 167 |
| 168 _effect = [[GLKBaseEffect alloc] init]; |
| 169 |
| 170 [self logGLErrorCode:@"setupGL begin"]; |
| 171 |
| 172 // Create 2 textures in the array |_textureIds| |
| 173 glGenTextures(2, _textureIds); |
| 174 |
| 175 [self logGLErrorCode:@"setupGL init"]; |
| 176 // Initialize each texture |
| 177 [self bindTextureForIOS:_textureIds[0]]; |
| 178 glBindTexture(GL_TEXTURE_2D, 0); |
| 179 |
| 180 [self bindTextureForIOS:_textureIds[1]]; |
| 181 glBindTexture(GL_TEXTURE_2D, 0); |
| 182 |
| 183 [self logGLErrorCode:@"setupGL textureComplete"]; |
| 184 |
| 185 // Texture 0 is the HOST Desktop layer, and will always replace what is in the |
| 186 // draw context |
| 187 [_effect texture2d0].target = GLKTextureTarget2D; |
| 188 [_effect texture2d0].name = _textureIds[0]; |
| 189 [_effect texture2d0].envMode = GLKTextureEnvModeReplace; |
| 190 [_effect texture2d0].enabled = GL_TRUE; |
| 191 |
| 192 // Texuture 1 is the Cursor layer, and is stamped on top of texture 0 as a |
| 193 // transparent image |
| 194 [_effect texture2d1].target = GLKTextureTarget2D; |
| 195 [_effect texture2d1].name = _textureIds[1]; |
| 196 [_effect texture2d1].envMode = GLKTextureEnvModeDecal; |
| 197 [_effect texture2d1].enabled = GL_TRUE; |
| 198 } |
| 199 |
| 200 // Override UIViewController |
| 201 - (void)viewDidUnload { |
| 202 [super viewDidUnload]; |
| 203 [self tearDownGL]; |
| 204 |
| 205 if ([EAGLContext currentContext] == _context) { |
| 206 [EAGLContext setCurrentContext:nil]; |
| 207 } |
| 208 _context = nil; |
| 209 } |
| 210 |
| 211 - (void)tearDownGL { |
| 212 [EAGLContext setCurrentContext:_context]; |
| 213 // Release 2 GL Textures |
| 214 glDeleteTextures(2, _textureIds); |
| 215 } |
| 216 |
| 217 // Override UIViewController |
| 218 - (void)viewWillAppear:(BOOL)animated { |
| 219 [super viewWillAppear:NO]; |
| 220 |
| 221 [self setFrameForControls]; |
| 222 } |
| 223 |
| 224 // Override UIViewController |
| 225 - (void)viewWillDisappear:(BOOL)animated { |
| 226 [super viewWillDisappear:NO]; |
| 227 NSArray* viewControllers = self.navigationController.viewControllers; |
| 228 if (viewControllers.count > 1 && |
| 229 [viewControllers objectAtIndex:viewControllers.count - 2] == self) { |
| 230 // View is disappearing because a new view controller was pushed onto the |
| 231 // stack |
| 232 } else if ([viewControllers indexOfObject:self] == NSNotFound) { |
| 233 // View is disappearing because it was popped from the stack |
| 234 [_controller disconnectFromHost]; |
| 235 } |
| 236 } |
| 237 |
| 238 // GL Binding Context requires some specific flags for the type of textures |
| 239 // being drawn |
| 240 - (void)bindTextureForIOS:(GLuint)glName { |
| 241 glBindTexture(GL_TEXTURE_2D, glName); |
| 242 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| 243 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| 244 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| 245 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| 246 } |
| 247 |
| 248 // Callback from |_updateDisplayTimer|. Occurs a short time after the form has |
| 249 // loaded. Begin the connection to the host. |
| 250 - (void)checkConnectStatus { |
| 251 if (_updateDisplayTimer) { |
| 252 [_updateDisplayTimer invalidate]; |
| 253 _updateDisplayTimer = nil; |
| 254 } |
| 255 |
| 256 _bbiHostAndStatus.title = _host.hostName; |
| 257 [_busyIndicator startAnimating]; |
| 258 |
| 259 NSString* authToken = [_authorization.parameters valueForKey:@"access_token"]; |
| 260 |
| 261 if (authToken == nil) { |
| 262 authToken = [_authorization authorizationTokenKey]; |
| 263 } |
| 264 |
| 265 [_controller connectToHost:[_authorization userEmail] |
| 266 authToken:authToken |
| 267 jabberId:_host.jabberId |
| 268 hostId:_host.hostId |
| 269 publicKey:_host.publicKey |
| 270 delegate:self]; |
| 271 } |
| 272 |
| 273 // @protocol PinEntryViewControllerDelegate |
| 274 // Return the PIN input by User, indicate if the User should be prompted to |
| 275 // re-enter the pin in the future |
| 276 - (void)connectToHostWithPin:(NSString*)hostPin |
| 277 shouldPrompt:(BOOL)shouldPrompt { |
| 278 const HostPreferences* hostPrefs = |
| 279 [[DataStore sharedStore] getHostForId:_host.hostId]; |
| 280 if (!hostPrefs) { |
| 281 hostPrefs = [[DataStore sharedStore] createHost:_host.hostId]; |
| 282 } |
| 283 if (hostPrefs) { |
| 284 hostPrefs.hostPin = hostPin; |
| 285 hostPrefs.askForPin = [NSNumber numberWithBool:shouldPrompt]; |
| 286 [[DataStore sharedStore] saveChanges]; |
| 287 } |
| 288 |
| 289 [[_pinEntry presentingViewController] dismissViewControllerAnimated:NO |
| 290 completion:NULL]; |
| 291 _pinEntry = nil; |
| 292 |
| 293 [self doConnectToHostWithPin:hostPin createPairing:!shouldPrompt]; |
| 294 } |
| 295 |
| 296 // @protocol PinEntryViewControllerDelegate |
| 297 // Returns if the user canceled while entering their PIN |
| 298 - (void)cancelledConnectToHostWithPin { |
| 299 [[_pinEntry presentingViewController] dismissViewControllerAnimated:NO |
| 300 completion:NULL]; |
| 301 _pinEntry = nil; |
| 302 |
| 303 [self.navigationController popViewControllerAnimated:YES]; |
| 304 } |
| 305 |
| 306 - (void)setHostDetails:(Host*)host |
| 307 authorization:(GTMOAuth2Authentication*)authorization { |
| 308 _host = host; |
| 309 _authorization = authorization; |
| 310 } |
| 311 |
| 312 // Occurs after the user has entered thier PIN. If Pairing is already |
| 313 // established, PIN entry may be skipped. |
| 314 - (void)doConnectToHostWithPin:(NSString*)hostPin |
| 315 createPairing:(BOOL)createPair { |
| 316 [_controller authenticationResponse:hostPin createPair:createPair]; |
| 317 } |
| 318 |
| 319 // Converts a point in the the CLIENT resolution to a similar point in the HOST |
| 320 // resolution. Additionally, CLIENT resolution is expressed in float values |
| 321 // while HOST opperates in integer values. |
| 322 - (BOOL)convertTouchPointToMousePoint:(CGPoint)touchPoint |
| 323 targetPoint:(webrtc::DesktopVector&)mousePoint { |
| 324 if (![self isPointInScene:touchPoint]) { |
| 325 return NO; |
| 326 } |
| 327 |
| 328 // A touch location occurs in respect to the user's entire view surface. |
| 329 |
| 330 // The GL Context is upside down from the User's perspective so flip it. |
| 331 CGPoint glOrientedTouchPoint = |
| 332 CGPointMake(touchPoint.x, _contentSize.height() - touchPoint.y); |
| 333 |
| 334 // The GL surface generally is not at the same origination point as the touch, |
| 335 // so translate by the scene's position. |
| 336 CGPoint glOrientedPointInRespectToFrame = |
| 337 CGPointMake(glOrientedTouchPoint.x - _scenePosition.x, |
| 338 glOrientedTouchPoint.y - _scenePosition.y); |
| 339 |
| 340 // The perspective exists in relative to the CLIENT resoluation at 1:1, zoom |
| 341 // our persepective so we are relative to the HOST at 1:1 |
| 342 CGPoint glOrientedPointInFrame = |
| 343 CGPointMake(glOrientedPointInRespectToFrame.x / _scenePosition.z, |
| 344 glOrientedPointInRespectToFrame.y / _scenePosition.z); |
| 345 |
| 346 // Finally, flip the perspective back over to the Users, but this time in |
| 347 // respect to the HOST desktop. Floor to ensure the result is always in |
| 348 // frame. |
| 349 CGPoint deskTopOrientedPointInFrame = |
| 350 CGPointMake(floorf(glOrientedPointInFrame.x), |
| 351 floorf(_frameSize.height() - glOrientedPointInFrame.y)); |
| 352 |
| 353 // Convert from float to integer |
| 354 mousePoint.set(deskTopOrientedPointInFrame.x, deskTopOrientedPointInFrame.y); |
| 355 |
| 356 return CGRectContainsPoint( |
| 357 CGRectMake(0, 0, _frameSize.width(), _frameSize.height()), |
| 358 deskTopOrientedPointInFrame); |
| 359 } |
| 360 |
| 361 // Converts a point in the the HOST resolution to a similar point in the CLIENT |
| 362 // resolution. Additionally, CLIENT resolution is expressed in float values |
| 363 // while HOST opperates in integer values. |
| 364 - (BOOL)convertMousePointToTouchPoint:(const webrtc::DesktopVector&)mousePoint |
| 365 targetPoint:(CGPoint&)touchPoint { |
| 366 |
| 367 // A mouse point is in respect to the desktop frame. |
| 368 |
| 369 // Flip the perspective back over to the Users, in |
| 370 // respect to the HOST desktop. |
| 371 CGPoint deskTopOrientedPointInFrame = |
| 372 CGPointMake(mousePoint.x(), _frameSize.height() - mousePoint.y()); |
| 373 |
| 374 // The perspective exists in relative to the CLIENT resoluation at 1:1, zoom |
| 375 // our persepective so we are relative to the HOST at 1:1 |
| 376 CGPoint glOrientedPointInFrame = |
| 377 CGPointMake(deskTopOrientedPointInFrame.x * _scenePosition.z, |
| 378 deskTopOrientedPointInFrame.y * _scenePosition.z); |
| 379 |
| 380 // The GL surface generally is not at the same origination point as the touch, |
| 381 // so translate by the scene's position. |
| 382 CGPoint glOrientedPointInRespectToFrame = |
| 383 CGPointMake(glOrientedPointInFrame.x + _scenePosition.x, |
| 384 glOrientedPointInFrame.y + _scenePosition.y); |
| 385 |
| 386 // The GL Context is upside down from the User's perspective so flip it. |
| 387 CGPoint glOrientedTouchPoint = |
| 388 CGPointMake(glOrientedPointInRespectToFrame.x, |
| 389 _contentSize.height() - glOrientedPointInRespectToFrame.y); |
| 390 |
| 391 // Convert from float to integer |
| 392 touchPoint.x = floorf(glOrientedTouchPoint.x); |
| 393 touchPoint.y = floorf(glOrientedTouchPoint.y); |
| 394 |
| 395 return [self isPointInScene:touchPoint]; |
| 396 } |
| 397 |
| 398 // Resize the view of the desktop - Zoon in/out. This can occur during a Pan. |
| 399 - (IBAction)pinchGestureTriggered:(UIPinchGestureRecognizer*)sender { |
| 400 if ([sender state] == UIGestureRecognizerStateChanged) { |
| 401 [self panAndZoom:CGPointMake(0.0, 0.0) scaleBy:sender.scale]; |
| 402 |
| 403 sender.scale = 1.0; // reset scale so next iteration is a relative ratio |
| 404 } |
| 405 } |
| 406 |
| 407 // Left button click |
| 408 - (IBAction)tapGestureTriggered:(UITapGestureRecognizer*)sender { |
| 409 if ([self isPointInScene:[sender locationInView:self.view]]) { |
| 410 [Utility leftClickOn:_controller at:_mousePosition]; |
| 411 } |
| 412 } |
| 413 |
| 414 // Change position of scene. This can occur during a pinch or longpress. |
| 415 // Or perform a Mouse Wheel Scroll |
| 416 - (IBAction)panGestureTriggered:(UIPanGestureRecognizer*)sender { |
| 417 CGPoint translation = [sender translationInView:self.view]; |
| 418 |
| 419 // If we start with 2 touches, and the pinch gesture is not in progress yet, |
| 420 // then disable it, so mouse scrolling and zoom do not occur at the same |
| 421 // time. |
| 422 if ([sender numberOfTouches] == 2 && [sender state] == |
| 423 UIGestureRecognizerStateBegan && |
| 424 !(_pinchRecognizer.state == UIGestureRecognizerStateBegan || |
| 425 _pinchRecognizer.state == UIGestureRecognizerStateChanged)) { |
| 426 _pinchRecognizer.enabled = NO; |
| 427 } |
| 428 |
| 429 if (!_pinchRecognizer.enabled) { |
| 430 // Began with 2 touches, so this is a scrool event |
| 431 translation.x *= kMouseWheelSensitivity; |
| 432 translation.y *= kMouseWheelSensitivity; |
| 433 [Utility mouseScroll:_controller |
| 434 at:_mousePosition |
| 435 delta:webrtc::DesktopVector(translation.x, translation.y)]; |
| 436 } else { |
| 437 // Did not begin with 2 touches, pan event |
| 438 if ([sender state] == UIGestureRecognizerStateChanged) { |
| 439 CGPoint translation = [sender translationInView:self.view]; |
| 440 |
| 441 [self panAndZoom:translation scaleBy:1.0]; |
| 442 |
| 443 } else if ([sender state] == UIGestureRecognizerStateEnded) { |
| 444 // After user removes their fingers from the screen, apply an acceleration |
| 445 // effect |
| 446 _panVelocity = [sender velocityInView:self.view]; |
| 447 } |
| 448 } |
| 449 |
| 450 // Finished the event chain |
| 451 if (!([sender state] == UIGestureRecognizerStateBegan || |
| 452 [sender state] == UIGestureRecognizerStateChanged)) { |
| 453 _pinchRecognizer.enabled = YES; |
| 454 } |
| 455 |
| 456 // Reset translation so next iteration is relative. |
| 457 [sender setTranslation:CGPointZero inView:self.view]; |
| 458 } |
| 459 |
| 460 // Click-Drag mouse operation. This can occur during a Pan. |
| 461 - (IBAction)longPressGestureTriggered:(UILongPressGestureRecognizer*)sender { |
| 462 |
| 463 if ([sender state] == UIGestureRecognizerStateBegan) { |
| 464 [_controller mouseAction:_mousePosition |
| 465 wheelDelta:webrtc::DesktopVector(0, 0) |
| 466 whichButton:1 |
| 467 buttonDown:YES]; |
| 468 } else if (!([sender state] == UIGestureRecognizerStateBegan || |
| 469 [sender state] == UIGestureRecognizerStateChanged)) { |
| 470 [_controller mouseAction:_mousePosition |
| 471 wheelDelta:webrtc::DesktopVector(0, 0) |
| 472 whichButton:1 |
| 473 buttonDown:NO]; |
| 474 } |
| 475 } |
| 476 |
| 477 // Right mouse button click |
| 478 - (IBAction)twoFingerTapGestureTriggered:(UITapGestureRecognizer*)sender { |
| 479 if ([self isPointInScene:[sender locationInView:self.view]]) { |
| 480 [Utility rightClickOn:_controller at:_mousePosition]; |
| 481 } |
| 482 } |
| 483 |
| 484 // Middle button click |
| 485 - (IBAction)threeFingerTapGestureTriggered:(UITapGestureRecognizer*)sender { |
| 486 |
| 487 if ([self isPointInScene:[sender locationInView:self.view]]) { |
| 488 [Utility middleClickOn:_controller at:_mousePosition]; |
| 489 } |
| 490 } |
| 491 |
| 492 // Show keyboard or navigation toolbar |
| 493 - (IBAction)threeFingerPanGestureTriggered:(UIPanGestureRecognizer*)sender { |
| 494 if ([sender state] == UIGestureRecognizerStateChanged) { |
| 495 CGPoint translation = [sender translationInView:self.view]; |
| 496 if (translation.y > 0) { |
| 497 // Swiped down |
| 498 [self.navigationController setNavigationBarHidden:NO animated:YES]; |
| 499 } else if (translation.y < 0) { |
| 500 // Swiped up |
| 501 [_keyEntryView becomeFirstResponder]; |
| 502 } |
| 503 [sender setTranslation:CGPointZero inView:self.view]; |
| 504 } |
| 505 } |
| 506 |
| 507 // Do navigation 'back' |
| 508 - (IBAction)barBtnNavigationBackPressed:(id)sender { |
| 509 [self.navigationController popViewControllerAnimated:YES]; |
| 510 } |
| 511 |
| 512 // Display the keyboard |
| 513 - (IBAction)barBtnKeyboardPressed:(id)sender { |
| 514 [_keyEntryView becomeFirstResponder]; |
| 515 } |
| 516 |
| 517 // Display the navigation bar |
| 518 - (IBAction)barBtnToolBarHidePressed:(id)sender { |
| 519 [self showToolbar:(_viewToolbar.frame.origin.y < 0)]; // Toolbar is either on |
| 520 // screen or off screen |
| 521 } |
| 522 |
| 523 // @protocol UIBarPositioningDelegate |
| 524 // Place the toolbar at the top of the screen |
| 525 - (UIBarPosition)positionForBar:(id<UIBarPositioning>)bar { |
| 526 return UIBarPositionTopAttached; |
| 527 } |
| 528 |
| 529 // Override UIResponder |
| 530 // When any gesture beings, remove any acceleration effects currently being |
| 531 // applied. Example, Panning view and let it shoot off into the distance, but |
| 532 // then I see a spot I'm interested in so I will touch to capture that locations |
| 533 // focus. |
| 534 - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { |
| 535 [self updatePanVelocityShouldCancel:YES]; |
| 536 [super touchesBegan:touches withEvent:event]; |
| 537 } |
| 538 |
| 539 // @protocol UIGestureRecognizerDelegate |
| 540 // Allow panning and zooming to occur simultaniously. |
| 541 // Allow panning and long press to occur simultaniously. |
| 542 // Pinch requires 2 touches, and long press requires a single touch, so they are |
| 543 // mutually exclusive regardless of if panning is the initiating guesture |
| 544 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer |
| 545 shouldRecognizeSimultaneouslyWithGestureRecognizer: |
| 546 (UIGestureRecognizer*)otherGestureRecognizer { |
| 547 if (gestureRecognizer == _pinchRecognizer || |
| 548 (gestureRecognizer == _panRecognizer)) { |
| 549 if (otherGestureRecognizer == _pinchRecognizer || |
| 550 otherGestureRecognizer == _panRecognizer) { |
| 551 return YES; |
| 552 } |
| 553 } |
| 554 |
| 555 if (gestureRecognizer == _longPressRecognizer || |
| 556 gestureRecognizer == _panRecognizer) { |
| 557 if (otherGestureRecognizer == _longPressRecognizer || |
| 558 otherGestureRecognizer == _panRecognizer) { |
| 559 return YES; |
| 560 } |
| 561 } |
| 562 return NO; |
| 563 } |
| 564 |
| 565 // @protocol ClientControllerDelegate |
| 566 // Prompt the user for their PIN if pairing has not already been established |
| 567 - (void)requestHostPin:(BOOL)pairingSupported { |
| 568 BOOL requestPin = YES; |
| 569 const HostPreferences* hostPrefs = |
| 570 [[DataStore sharedStore] getHostForId:_host.hostId]; |
| 571 if (hostPrefs) { |
| 572 requestPin = [hostPrefs.askForPin boolValue]; |
| 573 if (!requestPin) { |
| 574 if (hostPrefs.hostPin == nil || hostPrefs.hostPin.length == 0) { |
| 575 requestPin = YES; |
| 576 } |
| 577 } |
| 578 } |
| 579 if (requestPin == YES) { |
| 580 _pinEntry = [[PinEntryViewController alloc] init]; |
| 581 if (_pinEntry) { |
| 582 [_pinEntry setDelegate:self]; |
| 583 [_pinEntry setHostName:_host.hostName]; |
| 584 [_pinEntry setShouldPrompt:YES]; |
| 585 [_pinEntry setPairingSupported:pairingSupported]; |
| 586 |
| 587 [self presentViewController:_pinEntry animated:YES completion:nil]; |
| 588 } |
| 589 } else { |
| 590 [self doConnectToHostWithPin:hostPrefs.hostPin |
| 591 createPairing:pairingSupported]; |
| 592 } |
| 593 } |
| 594 |
| 595 // @protocol ClientControllerDelegate |
| 596 // Occurs when a connection to a HOST is established successfully |
| 597 - (void)connected { |
| 598 [_busyIndicator stopAnimating]; |
| 599 } |
| 600 |
| 601 // @protocol ClientControllerDelegate |
| 602 // Occurs when a connection to a HOST has failed |
| 603 - (void)connectionFailed:(NSString*)errorMessage { |
| 604 [_busyIndicator stopAnimating]; |
| 605 NSString* errorMsg; |
| 606 if ([_controller isConnected]) { |
| 607 errorMsg = @"Lost Connection"; |
| 608 } else { |
| 609 errorMsg = @"Unable to connect"; |
| 610 } |
| 611 [Utility showAlert:errorMsg message:errorMessage]; |
| 612 [self.navigationController popViewControllerAnimated:YES]; |
| 613 } |
| 614 |
| 615 // @protocol ClientControllerDelegate |
| 616 - (void)connectionStatus:(NSString*)statusMessage { |
| 617 NSMutableString* hostStatus = [[NSMutableString alloc] init]; |
| 618 [hostStatus appendFormat:@"%@ - %@", _host.hostName, statusMessage]; |
| 619 _bbiHostAndStatus.title = hostStatus; |
| 620 } |
| 621 |
| 622 // @protocol ClientControllerDelegate |
| 623 // Copy the updated regions to a backing store to be consumed by the GL Context |
| 624 // on a different thread. A region is stored in disjoint memory locations, and |
| 625 // must be transformed to a contiguous memory buffer for a GL Texture write. |
| 626 // /-----\ |
| 627 // | 2-4| This buffer is 5x3 bytes large, a region exists at bytes 2 to 4 and |
| 628 // | 7-9| bytes 7 to 9. The region is extracted to a new continguous buffer |
| 629 // | | of 6 bytes in length. |
| 630 // \-----/ |
| 631 // More than 1 region may exist in the frame from each call, in which case a new |
| 632 // buffer is created for each region |
| 633 - (void)applyFrame:(const webrtc::DesktopSize&)size |
| 634 stride:(NSInteger)stride |
| 635 data:(uint8_t*)data |
| 636 regions:(const std::vector<webrtc::DesktopRect>&)regions { |
| 637 |
| 638 #ifdef DEBUG |
| 639 _applyCount++; |
| 640 #endif // DEBUG |
| 641 |
| 642 // The HOST can change resolution. This always occurs on the first call to |
| 643 // |applyFrame| |
| 644 if (_frameSize.width() != size.width() || |
| 645 _frameSize.height() != size.height()) { |
| 646 _frameSize.set(size.width(), size.height()); |
| 647 |
| 648 _scenePosition.x = 0; |
| 649 _scenePosition.y = 0; |
| 650 |
| 651 CGPoint ratios = [self pixelRatio]; |
| 652 |
| 653 float verticalPixelScaleRatio = 1; |
| 654 |
| 655 // If the new resolution is larger then the old size, increase the zoom to |
| 656 // fit the entire vertical height in the CLIENT resolution height. |
| 657 if (_scenePosition.z < ratios.y) { |
| 658 verticalPixelScaleRatio = 1 / _scenePosition.z; // keep the current scale |
| 659 } else { |
| 660 verticalPixelScaleRatio = |
| 661 ratios.y / _scenePosition.z; // make scale small |
| 662 // enough so it will |
| 663 // fit vertical height |
| 664 } |
| 665 |
| 666 _isAnchorLeft = YES; |
| 667 _isAnchorBottom = YES; |
| 668 _isAnchorTop = YES; |
| 669 _isAnchorRight = YES; |
| 670 |
| 671 [self panAndZoom:CGPointMake(0.0, 0.0) scaleBy:verticalPixelScaleRatio]; |
| 672 _sizeChanged = YES; |
| 673 |
| 674 webrtc::DesktopVector newMouseLocation; |
| 675 if ([self convertTouchPointToMousePoint: |
| 676 CGPointMake(_contentSize.width() / 2, _contentSize.height() / 2) |
| 677 targetPoint:newMouseLocation]) { |
| 678 _mousePosition.set(newMouseLocation.x(), newMouseLocation.y()); |
| 679 } |
| 680 |
| 681 #if DEBUG |
| 682 NSLog(@"resized frame:%d:%d scale:%f", |
| 683 _frameSize.width(), |
| 684 _frameSize.height(), |
| 685 _scenePosition.z); |
| 686 #endif // DEBUG |
| 687 } |
| 688 |
| 689 [_glBufferLock lock]; // going to make changes to |_glRegions| |
| 690 |
| 691 uint32_t src_stride = stride; |
| 692 |
| 693 for (uint32_t i = 0; i < regions.size(); i++) { |
| 694 scoped_ptr<GLRegion> region(new GLRegion()); |
| 695 |
| 696 if (region.get()) { |
| 697 webrtc::DesktopRect rect = regions.at(i); |
| 698 |
| 699 webrtc::DesktopSize(rect.width(), rect.height()); |
| 700 region->offset.reset(new webrtc::DesktopVector(rect.left(), rect.top())); |
| 701 region->image.reset(new webrtc::BasicDesktopFrame( |
| 702 webrtc::DesktopSize(rect.width(), rect.height()))); |
| 703 |
| 704 if (region->image->data()) { |
| 705 uint32_t bytes_per_row = |
| 706 region->image->kBytesPerPixel * region->image->size().width(); |
| 707 |
| 708 uint32_t offset = |
| 709 (src_stride * region->offset->y()) + // row |
| 710 (region->offset->x() * region->image->kBytesPerPixel); // column |
| 711 |
| 712 uint8_t* src_buffer = data + offset; |
| 713 uint8_t* dst_buffer = region->image->data(); |
| 714 |
| 715 // row by row copy |
| 716 for (uint32_t j = 0; j < region->image->size().height(); j++) { |
| 717 memcpy(dst_buffer, src_buffer, bytes_per_row); |
| 718 dst_buffer += bytes_per_row; |
| 719 src_buffer += src_stride; |
| 720 } |
| 721 _glRegions.push_back(region.release()); |
| 722 } |
| 723 } |
| 724 } |
| 725 [_glBufferLock unlock]; // done makeing changes to |_glRegions| |
| 726 } |
| 727 |
| 728 // @protocol ClientControllerDelegate |
| 729 // Copy the delivered cursor to a backing store to be consumed by the GL Context |
| 730 // on a different thread. Note only the most recent cursor is of importance, |
| 731 // discard the previous cursor. |
| 732 - (void)applyCursor:(const webrtc::DesktopSize&)size |
| 733 hotspot:(const webrtc::DesktopVector&)hotspot |
| 734 cursorData:(uint8_t*)data { |
| 735 |
| 736 [_glCursorLock lock]; // going to make changes to |_cursor| |
| 737 |
| 738 // MouseCursor takes ownership of DesktopFrame |
| 739 _cursor.reset( |
| 740 new webrtc::MouseCursor(new webrtc::BasicDesktopFrame(size), hotspot)); |
| 741 |
| 742 if (_cursor->image().data()) { |
| 743 memcpy(_cursor->image().data(), |
| 744 data, |
| 745 size.width() * size.height() * _cursor->image().kBytesPerPixel); |
| 746 |
| 747 _needCursorRedraw = YES; |
| 748 } else { |
| 749 _cursor.reset(); |
| 750 _needCursorRedraw = NO; |
| 751 } |
| 752 |
| 753 [_glCursorLock unlock]; // done makeing changes to |_cursor| |
| 754 } |
| 755 |
| 756 // @protocol GLKViewDelegate |
| 757 // There is quite a few gotchas involved in working with this function. For |
| 758 // sanity purposes, I've just assumed calls to the function are on a different |
| 759 // thread which I've termed GL Context. Any varibles consumed by this function |
| 760 // should be thread safe. |
| 761 // |
| 762 // Clear Screen, update desktop, update cursor, define possition, and finally |
| 763 // present |
| 764 // |
| 765 // In general, avoid expensive work in this function to maximize frame rate. |
| 766 - (void)glkView:(GLKView*)view drawInRect:(CGRect)rect { |
| 767 |
| 768 [self updatePanVelocityShouldCancel:NO]; |
| 769 |
| 770 // Clear to black, to give the background color |
| 771 glClearColor(0.0, 0.0, 0.0, 1.0); |
| 772 glClear(GL_COLOR_BUFFER_BIT); |
| 773 |
| 774 [self logGLErrorCode:@"drawInRect bindBuffer"]; |
| 775 |
| 776 if (_glRegions.size() > 0 || _sizeChanged) { |
| 777 #ifdef DEBUG |
| 778 _drawCount++; |
| 779 #endif // DEBUG |
| 780 |
| 781 if (_sizeChanged) { |
| 782 // Update desktop is done by regions. But for the first call and when the |
| 783 // size changes (from the HOST changing desktop resoluation) we have to |
| 784 // reinitialize the textures. |
| 785 [self bindTextureForIOS:_textureIds[0]]; |
| 786 |
| 787 glTexImage2D(GL_TEXTURE_2D, |
| 788 0, |
| 789 GL_RGBA, |
| 790 _frameSize.width(), |
| 791 _frameSize.height(), |
| 792 0, |
| 793 GL_RGBA, |
| 794 GL_UNSIGNED_BYTE, |
| 795 NULL); |
| 796 |
| 797 glBindTexture(GL_TEXTURE_2D, 0); |
| 798 |
| 799 [self bindTextureForIOS:_textureIds[1]]; |
| 800 |
| 801 glTexImage2D(GL_TEXTURE_2D, |
| 802 0, |
| 803 GL_RGBA, |
| 804 _frameSize.width(), |
| 805 _frameSize.height(), |
| 806 0, |
| 807 GL_RGBA, |
| 808 GL_UNSIGNED_BYTE, |
| 809 NULL); |
| 810 |
| 811 glBindTexture(GL_TEXTURE_2D, 0); |
| 812 |
| 813 [self logGLErrorCode:@"drawInRect glTexImage2D"]; |
| 814 _sizeChanged = NO; |
| 815 } |
| 816 |
| 817 [self bindTextureForIOS:_textureIds[0]]; |
| 818 |
| 819 [_glBufferLock lock]; // going to make changes to |_glRegions| |
| 820 |
| 821 for (uint32_t i = 0; i < _glRegions.size(); i++) { |
| 822 |
| 823 GLRegion* region = _glRegions[i]; |
| 824 |
| 825 // |data| is properly ordered by |applyFrame| |
| 826 glTexSubImage2D(GL_TEXTURE_2D, |
| 827 0, |
| 828 region->offset->x(), |
| 829 region->offset->y(), |
| 830 region->image->size().width(), |
| 831 region->image->size().height(), |
| 832 GL_RGBA, |
| 833 GL_UNSIGNED_BYTE, |
| 834 region->image->data()); |
| 835 |
| 836 [self logGLErrorCode:@"drawInRect glTexSubImage2D"]; |
| 837 } |
| 838 |
| 839 _glRegions.clear(); |
| 840 |
| 841 [_glBufferLock unlock]; // done makeing changes to |_glRegions| |
| 842 |
| 843 // release bind - be nice |
| 844 glBindTexture(GL_TEXTURE_2D, 0); |
| 845 } |
| 846 |
| 847 // When the cursor needs to be redraw in a different spot then we must clear |
| 848 // the previous area. |
| 849 if (_cursor.get() != NULL && |
| 850 (_needCursorRedraw == YES || |
| 851 _cursorDrawnToGL.left() != _mousePosition.x() - _cursor->hotspot().x() || |
| 852 _cursorDrawnToGL.top() != _mousePosition.y() - _cursor->hotspot().y())) { |
| 853 |
| 854 [_glCursorLock lock]; // going to make changes to |_cursor| |
| 855 [self bindTextureForIOS:_textureIds[1]]; |
| 856 |
| 857 if (_cursorDrawnToGL.width() > 0 && _cursorDrawnToGL.height() > 0) { |
| 858 webrtc::BasicDesktopFrame transparentCursor(_cursorDrawnToGL.size()); |
| 859 |
| 860 if (transparentCursor.data() != NULL) { |
| 861 CHECK(transparentCursor.kBytesPerPixel == |
| 862 _cursor->image().kBytesPerPixel); |
| 863 memset(transparentCursor.data(), |
| 864 0, |
| 865 transparentCursor.stride() * transparentCursor.size().height()); |
| 866 |
| 867 glTexSubImage2D(GL_TEXTURE_2D, |
| 868 0, |
| 869 _cursorDrawnToGL.left(), |
| 870 _cursorDrawnToGL.top(), |
| 871 _cursorDrawnToGL.width(), |
| 872 _cursorDrawnToGL.height(), |
| 873 GL_RGBA, |
| 874 GL_UNSIGNED_BYTE, |
| 875 transparentCursor.data()); |
| 876 |
| 877 // there is no longer any cursor drawn to screen |
| 878 _cursorDrawnToGL = webrtc::DesktopRect::MakeXYWH(0, 0, 0, 0); |
| 879 } |
| 880 } |
| 881 |
| 882 if (_cursor.get() != NULL) { |
| 883 |
| 884 CGRect screen = |
| 885 CGRectMake(0.0, 0.0, _frameSize.width(), _frameSize.height()); |
| 886 CGRect cursor = CGRectMake(_mousePosition.x() - _cursor->hotspot().x(), |
| 887 _mousePosition.y() - _cursor->hotspot().y(), |
| 888 _cursor->image().size().width(), |
| 889 _cursor->image().size().height()); |
| 890 |
| 891 if (CGRectContainsRect(screen, cursor)) { |
| 892 _cursorDrawnToGL = webrtc::DesktopRect::MakeXYWH(cursor.origin.x, |
| 893 cursor.origin.y, |
| 894 cursor.size.width, |
| 895 cursor.size.height); |
| 896 |
| 897 glTexSubImage2D(GL_TEXTURE_2D, |
| 898 0, |
| 899 _cursorDrawnToGL.left(), |
| 900 _cursorDrawnToGL.top(), |
| 901 _cursorDrawnToGL.width(), |
| 902 _cursorDrawnToGL.height(), |
| 903 GL_RGBA, |
| 904 GL_UNSIGNED_BYTE, |
| 905 _cursor->image().data()); |
| 906 |
| 907 } else if (CGRectIntersectsRect(screen, rect)) { |
| 908 // Some of the cursor falls off screen, need to clip it |
| 909 CGRect intersection = CGRectIntersection(screen, cursor); |
| 910 _cursorDrawnToGL = |
| 911 webrtc::DesktopRect::MakeXYWH(intersection.origin.x, |
| 912 intersection.origin.y, |
| 913 intersection.size.width, |
| 914 intersection.size.height); |
| 915 |
| 916 webrtc::BasicDesktopFrame partialCursor(webrtc::DesktopSize( |
| 917 _cursorDrawnToGL.width(), _cursorDrawnToGL.height())); |
| 918 |
| 919 if (partialCursor.data()) { |
| 920 CHECK(partialCursor.kBytesPerPixel == |
| 921 _cursor->image().kBytesPerPixel); |
| 922 |
| 923 uint32_t src_stride = _cursor->image().stride(); |
| 924 uint32_t dst_stride = partialCursor.stride(); |
| 925 |
| 926 uint8_t* source = _cursor->image().data(); |
| 927 source += |
| 928 (static_cast<int32_t>(cursor.origin.y) - _cursorDrawnToGL.top()) * |
| 929 src_stride; |
| 930 source += (static_cast<int32_t>(cursor.origin.x) - |
| 931 _cursorDrawnToGL.left()) * |
| 932 _cursor->image().kBytesPerPixel; |
| 933 uint8_t* dst = partialCursor.data(); |
| 934 |
| 935 for (uint32_t y = 0; y < _cursorDrawnToGL.height(); y++) { |
| 936 memcpy(dst, source, dst_stride); |
| 937 source += src_stride; |
| 938 dst += dst_stride; |
| 939 } |
| 940 |
| 941 glTexSubImage2D(GL_TEXTURE_2D, |
| 942 0, |
| 943 _cursorDrawnToGL.left(), |
| 944 _cursorDrawnToGL.top(), |
| 945 _cursorDrawnToGL.width(), |
| 946 _cursorDrawnToGL.height(), |
| 947 GL_RGBA, |
| 948 GL_UNSIGNED_BYTE, |
| 949 partialCursor.data()); |
| 950 } |
| 951 } |
| 952 } |
| 953 |
| 954 glBindTexture(GL_TEXTURE_2D, 0); |
| 955 [_glCursorLock unlock]; // done makeing changes to |_cursor| |
| 956 |
| 957 _needCursorRedraw = NO; |
| 958 [self logGLErrorCode:@"drawInRect mouseDrawComplete"]; |
| 959 } |
| 960 |
| 961 // The scene is the entire CLIENT screen |
| 962 GLKMatrix4 projectionMatrix = GLKMatrix4MakeOrtho( |
| 963 0.0, _contentSize.width(), 0.0, _contentSize.height(), 1.0, -1.0); |
| 964 [_effect transform].projectionMatrix = projectionMatrix; |
| 965 |
| 966 // Start by using the entire scene |
| 967 GLKMatrix4 modelMatrix = GLKMatrix4Identity; |
| 968 |
| 969 // Position scene according to any panning or bounds |
| 970 modelMatrix = GLKMatrix4Translate( |
| 971 modelMatrix, _scenePosition.x, _scenePosition.y + kBottomMargin, 0.0); |
| 972 |
| 973 webrtc::DesktopSize currentSize = [self getCurrentSize]; |
| 974 CGPoint ratios = [self pixelRatio]; |
| 975 |
| 976 // Apply zoom |
| 977 modelMatrix = GLKMatrix4Scale( |
| 978 modelMatrix, |
| 979 _scenePosition.z / ratios.x, |
| 980 _scenePosition.z / ratios.y * |
| 981 (1.0 - |
| 982 (static_cast<float>(kTopMargin + _toolbarHeight + kBottomMargin) / |
| 983 static_cast<float>(currentSize.height()))), |
| 984 1.0); |
| 985 |
| 986 // We are directly above the sceen and looking down. |
| 987 GLKMatrix4 viewMatrix = GLKMatrix4MakeLookAt( |
| 988 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); // center view |
| 989 |
| 990 [_effect transform].modelviewMatrix = |
| 991 GLKMatrix4Multiply(viewMatrix, modelMatrix); |
| 992 |
| 993 [_effect prepareToDraw]; |
| 994 |
| 995 [self logGLErrorCode:@"drawInRect prepareToDrawComplete"]; |
| 996 |
| 997 glEnableVertexAttribArray(GLKVertexAttribPosition); |
| 998 glEnableVertexAttribArray(GLKVertexAttribTexCoord0); |
| 999 glEnableVertexAttribArray(GLKVertexAttribTexCoord1); |
| 1000 |
| 1001 // Define our scene space |
| 1002 glVertexAttribPointer(GLKVertexAttribPosition, |
| 1003 2, |
| 1004 GL_FLOAT, |
| 1005 GL_FALSE, |
| 1006 sizeof(TexturedVertex), |
| 1007 &(_quad.bl.geometryVertex)); |
| 1008 // Define the desktop plane |
| 1009 glVertexAttribPointer(GLKVertexAttribTexCoord0, |
| 1010 2, |
| 1011 GL_FLOAT, |
| 1012 GL_FALSE, |
| 1013 sizeof(TexturedVertex), |
| 1014 &(_quad.bl.textureVertex)); |
| 1015 // Define the cursor plane |
| 1016 glVertexAttribPointer(GLKVertexAttribTexCoord1, |
| 1017 2, |
| 1018 GL_FLOAT, |
| 1019 GL_FALSE, |
| 1020 sizeof(TexturedVertex), |
| 1021 &(_quad.bl.textureVertex)); |
| 1022 |
| 1023 // Draw! |
| 1024 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); |
| 1025 |
| 1026 [self logGLErrorCode:@"drawInRect exit"]; |
| 1027 |
| 1028 #ifdef DEBUG |
| 1029 _frameCount++; |
| 1030 |
| 1031 if (_frameCount % 150 == 0) { |
| 1032 NSLog(@"@drawInRect drawCount:%d ApplyCount%d", _drawCount, _applyCount); |
| 1033 } |
| 1034 #endif // DEBUG |
| 1035 } |
| 1036 |
| 1037 // Sometimes its necessary to read gl errors. This is called in various places |
| 1038 // while working in the GL Context |
| 1039 - (void)logGLErrorCode:(NSString*)funcName { |
| 1040 GLenum errorCode = 1; |
| 1041 |
| 1042 while (errorCode != 0) { |
| 1043 errorCode = glGetError(); // I don't know why this is returning an error |
| 1044 // ont he first call to this function, but if I |
| 1045 // don't read it, then stuff doesn't work... |
| 1046 #if DEBUG |
| 1047 if (errorCode != 0) { |
| 1048 NSLog(@"glerror in %@: %X", funcName, errorCode); |
| 1049 } |
| 1050 #endif // DEBUG |
| 1051 } |
| 1052 } |
| 1053 |
| 1054 // Position busy spinner in the middle of the frame |
| 1055 - (void)setFrameForControls { |
| 1056 CGRect mainFrameForSpinner = self.view.frame; |
| 1057 CGRect spinnerFrame = _busyIndicator.frame; |
| 1058 spinnerFrame.origin.x = |
| 1059 (mainFrameForSpinner.size.width / 2) - (spinnerFrame.size.width / 2); |
| 1060 spinnerFrame.origin.y = |
| 1061 (mainFrameForSpinner.size.height / 2) - (spinnerFrame.size.height / 2); |
| 1062 _busyIndicator.frame = spinnerFrame; |
| 1063 } |
| 1064 |
| 1065 // @protocol KeyInputDelegate |
| 1066 // Send keyboard input to HOST |
| 1067 - (void)keyboardActionKeyCode:(uint32_t)keyPressed isKeyDown:(BOOL)keyDown { |
| 1068 [_controller keyboardAction:keyPressed keyDown:keyDown]; |
| 1069 } |
| 1070 |
| 1071 // Return the client resolution in User's perspective |
| 1072 - (CGRect)getViewBounds { |
| 1073 CGRect curBounds = self.view.bounds; |
| 1074 if ((curBounds.size.height > curBounds.size.width) && |
| 1075 [Utility isInLandscapeMode]) { |
| 1076 float width = curBounds.size.height; |
| 1077 curBounds.size.height = curBounds.size.width; |
| 1078 curBounds.size.width = width; |
| 1079 } |
| 1080 return curBounds; |
| 1081 } |
| 1082 |
| 1083 // Update the CLIENT resoluation and draw scene size, account for margins |
| 1084 - (void)updateContentSize { |
| 1085 |
| 1086 CGRect viewBounds = [self getViewBounds]; |
| 1087 |
| 1088 viewBounds.size.height -= (kTopMargin + _toolbarHeight + kBottomMargin); |
| 1089 |
| 1090 _contentSize.set(viewBounds.size.width, viewBounds.size.height); |
| 1091 |
| 1092 TexturedQuad newQuad; |
| 1093 newQuad.bl.geometryVertex = CGPointMake(0.0, 0.0); |
| 1094 newQuad.br.geometryVertex = CGPointMake(_contentSize.width(), 0.0); |
| 1095 newQuad.tl.geometryVertex = CGPointMake(0.0, _contentSize.height()); |
| 1096 newQuad.tr.geometryVertex = |
| 1097 CGPointMake(_contentSize.width(), _contentSize.height()); |
| 1098 |
| 1099 newQuad.bl.textureVertex = CGPointMake(0.0, 1.0); |
| 1100 newQuad.br.textureVertex = CGPointMake(1.0, 1.0); |
| 1101 newQuad.tl.textureVertex = CGPointMake(0.0, 0.0); |
| 1102 newQuad.tr.textureVertex = CGPointMake(1.0, 0.0); |
| 1103 |
| 1104 _quad = newQuad; |
| 1105 } |
| 1106 |
| 1107 // Update the scene acceleration vector |
| 1108 - (void)updatePanVelocityShouldCancel:(bool)canceled { |
| 1109 |
| 1110 if (canceled) { |
| 1111 _panVelocity = CGPointMake(0.0, 0.0); |
| 1112 } |
| 1113 |
| 1114 BOOL inMotion = ((_panVelocity.x != 0.0) || (_panVelocity.y != 0.0)); |
| 1115 |
| 1116 _singleTapRecognizer.enabled = !inMotion; |
| 1117 _longPressRecognizer.enabled = !inMotion; |
| 1118 |
| 1119 if (inMotion) { |
| 1120 |
| 1121 uint32_t divisor = 50 / _scenePosition.z; |
| 1122 float reducer = .95; |
| 1123 |
| 1124 if (_panVelocity.x != 0.0 && ABS(_panVelocity.x) < divisor) { |
| 1125 _panVelocity = CGPointMake(0.0, _panVelocity.y); |
| 1126 } |
| 1127 |
| 1128 if (_panVelocity.y != 0.0 && ABS(_panVelocity.y) < divisor) { |
| 1129 _panVelocity = CGPointMake(_panVelocity.x, 0.0); |
| 1130 } |
| 1131 |
| 1132 [self panAndZoom:CGPointMake(_panVelocity.x / divisor, |
| 1133 _panVelocity.y / divisor) |
| 1134 scaleBy:1.0]; |
| 1135 |
| 1136 _panVelocity = |
| 1137 CGPointMake(_panVelocity.x * reducer, _panVelocity.y * reducer); |
| 1138 } |
| 1139 } |
| 1140 |
| 1141 // Callback from NSNotificationCenter when the User changes orientation |
| 1142 - (void)orientationChanged:(NSNotification*)note { |
| 1143 [self updateContentSize]; |
| 1144 [self panAndZoom:CGPointMake(0.0, 0.0) scaleBy:1.0]; |
| 1145 } |
| 1146 |
| 1147 // Returns the number of pixels displayed per device pixel when the scaleing is |
| 1148 // such that the entire frame would fit perfectly in content. Note the ratios |
| 1149 // are different for width and height, some people have multiple monitors, some |
| 1150 // have 16:9 or 4:3 while iPad is always single screen, but different iOS |
| 1151 // devices have different resoluations. |
| 1152 - (CGPoint)pixelRatio { |
| 1153 |
| 1154 CGPoint r = CGPointMake(static_cast<float>(_contentSize.width()) / |
| 1155 static_cast<float>(_frameSize.width()), |
| 1156 static_cast<float>(_contentSize.height()) / |
| 1157 static_cast<float>(_frameSize.height())); |
| 1158 return r; |
| 1159 } |
| 1160 |
| 1161 // Return the FrameSize in persepective of the CLIENT resolution |
| 1162 - (webrtc::DesktopSize)getCurrentSize { |
| 1163 webrtc::DesktopSize r(_frameSize.width() * _scenePosition.z, |
| 1164 _frameSize.height() * _scenePosition.z); |
| 1165 return r; |
| 1166 } |
| 1167 |
| 1168 // Applies translation and zoom originateing from |touch|. Translation is |
| 1169 // bounded to screen edges. Zooming is bounded on the lower side to the maximun |
| 1170 // of width and height, and on the upper side by a constant, experimentally |
| 1171 // chosen. |
| 1172 - (void)panAndZoom:(CGPoint)translation scaleBy:(float)ratio { |
| 1173 |
| 1174 CGPoint ratios = [self pixelRatio]; |
| 1175 |
| 1176 // New Scaleing factor bounded by a min and max |
| 1177 float resultScale = _scenePosition.z * ratio; |
| 1178 float scaleUpperBound = kMaxZoomSize; |
| 1179 float scaleLowerBound = MIN(ratios.x, ratios.y); |
| 1180 |
| 1181 if (resultScale < scaleLowerBound) { |
| 1182 resultScale = scaleLowerBound; |
| 1183 } else if (resultScale > scaleUpperBound) { |
| 1184 resultScale = scaleUpperBound; |
| 1185 } |
| 1186 |
| 1187 // The GL perspective is upside down in relation to the User's view, so flip |
| 1188 // the translation |
| 1189 translation.y = -translation.y; |
| 1190 |
| 1191 // These could be user options, but just using constants for now. |
| 1192 translation.x = |
| 1193 translation.x * kXAxisInversion * (1 / (ratios.x * kMouseSensitivity)); |
| 1194 translation.y = |
| 1195 translation.y * kYAxisInversion * (1 / (ratios.y * kMouseSensitivity)); |
| 1196 |
| 1197 CGPoint delta = CGPointMake(0, 0); |
| 1198 CGPoint scaleDelta = CGPointMake(0, 0); |
| 1199 |
| 1200 webrtc::DesktopSize currentSize = [self getCurrentSize]; |
| 1201 |
| 1202 // When bounded on the top or right, this point is where the scene must be |
| 1203 // positioned given its current deminsions |
| 1204 webrtc::DesktopVector bounds(_contentSize.width() - currentSize.width(), |
| 1205 _contentSize.height() - currentSize.height()); |
| 1206 |
| 1207 // There are rounding errors in the scope of this function, see the butterfly |
| 1208 // effect. In successsive calls, the resulting position isn't always exactly |
| 1209 // the calculated position. If we know we are Anchored, then go ahead and |
| 1210 // reposition it to the values above. |
| 1211 if (_isAnchorRight) { |
| 1212 _scenePosition = |
| 1213 GLKVector3Make(bounds.x(), _scenePosition.y, _scenePosition.z); |
| 1214 } |
| 1215 |
| 1216 if (_isAnchorTop) { |
| 1217 _scenePosition = |
| 1218 GLKVector3Make(_scenePosition.x, bounds.y(), _scenePosition.z); |
| 1219 } |
| 1220 |
| 1221 if (_scenePosition.z != resultScale) { |
| 1222 |
| 1223 // When scaling the scene, the origination of scaleing is the mouse's |
| 1224 // location. But when the frame is anchored, adjust the origination to the |
| 1225 // anchor point. |
| 1226 |
| 1227 CGPoint mousePositionInClientResolution; |
| 1228 [self convertMousePointToTouchPoint:_mousePosition |
| 1229 targetPoint:mousePositionInClientResolution]; |
| 1230 |
| 1231 if (_isAnchorLeft) { |
| 1232 mousePositionInClientResolution.x = 0; |
| 1233 } else if (_isAnchorRight) { |
| 1234 mousePositionInClientResolution.x = _contentSize.width(); |
| 1235 } |
| 1236 |
| 1237 if (_isAnchorTop) { |
| 1238 mousePositionInClientResolution.y = _contentSize.height(); |
| 1239 } else if (_isAnchorBottom) { |
| 1240 mousePositionInClientResolution.y = 0; |
| 1241 } |
| 1242 |
| 1243 scaleDelta.x -= [HostViewController |
| 1244 calculateScaleingDelta:ratio |
| 1245 size:currentSize.width() |
| 1246 position:_scenePosition.x |
| 1247 anchor:mousePositionInClientResolution.x]; |
| 1248 |
| 1249 scaleDelta.y -= [HostViewController |
| 1250 calculateScaleingDelta:ratio |
| 1251 size:currentSize.height() |
| 1252 position:_scenePosition.y |
| 1253 anchor:mousePositionInClientResolution.y]; |
| 1254 } |
| 1255 |
| 1256 delta.x = [HostViewController |
| 1257 calculatePositionDelta:_scenePosition.x |
| 1258 length:_contentSize.width() - currentSize.width() |
| 1259 translation:translation.x |
| 1260 scaleDelta:scaleDelta.x |
| 1261 isAnchoredLow:_isAnchorLeft |
| 1262 isAnchoredHigh:_isAnchorRight]; |
| 1263 |
| 1264 delta.y = [HostViewController |
| 1265 calculatePositionDelta:_scenePosition.y |
| 1266 length:_contentSize.height() - currentSize.height() |
| 1267 translation:translation.y |
| 1268 scaleDelta:scaleDelta.y |
| 1269 isAnchoredLow:_isAnchorBottom |
| 1270 isAnchoredHigh:_isAnchorTop]; |
| 1271 |
| 1272 delta.x = [HostViewController applyBoundsToFrameAxis:_scenePosition.x |
| 1273 delta:delta.x |
| 1274 lowerBound:bounds.x() + scaleDelta.x |
| 1275 upperBound:0]; |
| 1276 |
| 1277 delta.y = [HostViewController applyBoundsToFrameAxis:_scenePosition.y |
| 1278 delta:delta.y |
| 1279 lowerBound:bounds.y() + scaleDelta.y |
| 1280 upperBound:0]; |
| 1281 |
| 1282 BOOL isLeftAndRightAnchored = _isAnchorLeft && _isAnchorRight; |
| 1283 BOOL isTopAndBottomAnchored = _isAnchorTop && _isAnchorBottom; |
| 1284 |
| 1285 [self updateMousePositionAndAnchorsWithBounds:bounds translation:translation]; |
| 1286 |
| 1287 // If both anchors were lost, then keep the one that is easier to predict |
| 1288 if (isLeftAndRightAnchored && !_isAnchorLeft && !_isAnchorRight) { |
| 1289 delta.x = -_scenePosition.x; |
| 1290 _isAnchorLeft = YES; |
| 1291 } |
| 1292 |
| 1293 // If both anchors were lost, then keep the one that is easier to predict |
| 1294 if (isTopAndBottomAnchored && !_isAnchorTop && !_isAnchorBottom) { |
| 1295 delta.y = -_scenePosition.y; |
| 1296 _isAnchorBottom = YES; |
| 1297 } |
| 1298 |
| 1299 // FINALLY, update the scene's position |
| 1300 _scenePosition = GLKVector3Make( |
| 1301 _scenePosition.x + delta.x, _scenePosition.y + delta.y, resultScale); |
| 1302 |
| 1303 // Notify HOST that the mouse moved |
| 1304 [Utility moveMouse:_controller at:_mousePosition]; |
| 1305 } |
| 1306 |
| 1307 // When zoom is changed the scene is also translated to keep the |
| 1308 // origination point (the spot the user is touching) at the same place in the |
| 1309 // User's perspective. |
| 1310 + (float)calculateScaleingDelta:(float)scale |
| 1311 size:(float)size |
| 1312 position:(float)position |
| 1313 anchor:(float)anchor { |
| 1314 |
| 1315 float newSize = size * scale; |
| 1316 |
| 1317 float scaleXBy = fabs(position - anchor) / newSize; |
| 1318 |
| 1319 float delta = (newSize - size) * scaleXBy; |
| 1320 |
| 1321 return delta; |
| 1322 } |
| 1323 |
| 1324 // Determine overall |_scenePosition| Delta for an axis |
| 1325 + (int)calculatePositionDelta:(int)position |
| 1326 length:(int)length |
| 1327 translation:(int)translation |
| 1328 scaleDelta:(int)scaleDelta |
| 1329 isAnchoredLow:(BOOL)isAnchoredLow |
| 1330 isAnchoredHigh:(BOOL)isAnchoredHigh { |
| 1331 |
| 1332 if (isAnchoredLow && isAnchoredHigh) { |
| 1333 // center the view |
| 1334 return (length / 2) - position; |
| 1335 } else if (isAnchoredLow) { |
| 1336 return 0; |
| 1337 } else if (isAnchoredHigh) { |
| 1338 return scaleDelta; |
| 1339 } else { |
| 1340 |
| 1341 return translation + scaleDelta; |
| 1342 } |
| 1343 } |
| 1344 |
| 1345 // |position + delta| is snapped to the bounds, return the delta in respect to |
| 1346 // the bounding. |
| 1347 + (int)applyBoundsToFrameAxis:(float)position |
| 1348 delta:(int)delta |
| 1349 lowerBound:(int)lowerBound |
| 1350 upperBound:(int)upperBound { |
| 1351 int result = position + delta; |
| 1352 |
| 1353 if (lowerBound < upperBound) { // the view is larger than the bounds |
| 1354 if (result > upperBound) { |
| 1355 result = upperBound; |
| 1356 } else if (result < lowerBound) { |
| 1357 result = lowerBound; |
| 1358 } |
| 1359 } else { // the view is smaller than the bounds |
| 1360 if (result < upperBound) { |
| 1361 result = upperBound; |
| 1362 } else if (result > lowerBound) { |
| 1363 result = lowerBound; |
| 1364 } |
| 1365 } |
| 1366 return result - position; |
| 1367 } |
| 1368 |
| 1369 // Return |nextPosition| when it is anchored and still in the respective 1/2 of |
| 1370 // the screen. When |nextPosition| is outside scene's edge, snap to edge. |
| 1371 // Otherwise return |centerPosition| |
| 1372 + (int)calculateMousePosition:(int)nextPosition |
| 1373 maxPosition:(int)maxPosition |
| 1374 centerPosition:(int)centerPosition |
| 1375 isAnchoredLow:(BOOL)isAnchoredLow |
| 1376 isAnchoredHigh:(BOOL)isAnchoredHigh { |
| 1377 |
| 1378 if (nextPosition < 0) { |
| 1379 return 0; |
| 1380 } |
| 1381 if (nextPosition > maxPosition - 1) { |
| 1382 return maxPosition - 1; |
| 1383 } |
| 1384 |
| 1385 if ((isAnchoredLow && nextPosition <= centerPosition) || |
| 1386 (isAnchoredHigh && nextPosition >= centerPosition)) { |
| 1387 return nextPosition; |
| 1388 } |
| 1389 |
| 1390 return centerPosition; |
| 1391 } |
| 1392 |
| 1393 // If the mouse is at an edge, remove any existing velocity from vector |
| 1394 - (void)cancelVelocityWhenAtEdge { |
| 1395 if (_panVelocity.x != 0.0) { |
| 1396 if (_mousePosition.x() == 0 || |
| 1397 _mousePosition.x() == _frameSize.width() - 1) { |
| 1398 _panVelocity = CGPointMake(0.0, _panVelocity.y); |
| 1399 } |
| 1400 } |
| 1401 |
| 1402 if (_panVelocity.y != 0.0) { |
| 1403 if (_mousePosition.y() == 0 || |
| 1404 _mousePosition.y() == _frameSize.height() - 1) { |
| 1405 _panVelocity = CGPointMake(_panVelocity.x, 0.0); |
| 1406 } |
| 1407 } |
| 1408 } |
| 1409 |
| 1410 // Mouse is tracked in the prespective of the HOST desktop, but the projection |
| 1411 // to the user is in the prespective of the CLIENT resolution. Find the HOST |
| 1412 // position that is the center of the current CLIENT view. If the mouse is in |
| 1413 // the half of the CLIENT screen that is closest to an anchor, then move the |
| 1414 // mouse, otherwise the mouse should be centered. |
| 1415 - (void)updateMousePositionAndAnchorsWithBounds: |
| 1416 (const webrtc::DesktopVector&)bounds |
| 1417 translation:(CGPoint)translation { |
| 1418 webrtc::DesktopVector centerMouseLocation; |
| 1419 [self convertTouchPointToMousePoint:CGPointMake(_contentSize.width() / 2, |
| 1420 _contentSize.height() / 2) |
| 1421 targetPoint:centerMouseLocation]; |
| 1422 |
| 1423 webrtc::DesktopVector predictedMousePosition( |
| 1424 _mousePosition.x() - translation.x, _mousePosition.y() + translation.y); |
| 1425 |
| 1426 _mousePosition |
| 1427 .set([HostViewController calculateMousePosition:predictedMousePosition.x() |
| 1428 maxPosition:_frameSize.width() |
| 1429 centerPosition:centerMouseLocation.x() |
| 1430 isAnchoredLow:_isAnchorLeft |
| 1431 isAnchoredHigh:_isAnchorRight], |
| 1432 [HostViewController calculateMousePosition:predictedMousePosition.y() |
| 1433 maxPosition:_frameSize.height() |
| 1434 centerPosition:centerMouseLocation.y() |
| 1435 isAnchoredLow:_isAnchorTop |
| 1436 isAnchoredHigh:_isAnchorBottom]); |
| 1437 [self cancelVelocityWhenAtEdge]; |
| 1438 |
| 1439 _isAnchorLeft = (bounds.x() >= 0) || |
| 1440 ((_scenePosition.x) == 0 && |
| 1441 predictedMousePosition.x() < centerMouseLocation.x()); |
| 1442 |
| 1443 _isAnchorRight = (bounds.x() >= 0) || |
| 1444 (_scenePosition.x == bounds.x() && |
| 1445 predictedMousePosition.x() > centerMouseLocation.x()); |
| 1446 _isAnchorTop = (bounds.y() >= 0) || |
| 1447 (_scenePosition.y == bounds.y() && |
| 1448 predictedMousePosition.y() < centerMouseLocation.y()); |
| 1449 _isAnchorBottom = (bounds.y() >= 0) || |
| 1450 ((_scenePosition.y) == 0 && |
| 1451 predictedMousePosition.y() > centerMouseLocation.y()); |
| 1452 } |
| 1453 |
| 1454 - (BOOL)isPointInScene:(CGPoint)point { |
| 1455 CGRect frame = CGRectMake(0, |
| 1456 kTopMargin + _toolbarHeight, |
| 1457 _contentSize.width(), |
| 1458 _contentSize.height()); |
| 1459 return CGRectContainsPoint(frame, point); |
| 1460 } |
| 1461 |
| 1462 // Animate the toolbar moving on or offscreen |
| 1463 - (void)showToolbar:(BOOL)visible { |
| 1464 if (visible && (_viewToolbar.frame.origin.y >= 0)) { |
| 1465 return; |
| 1466 } |
| 1467 |
| 1468 CGRect frame = [_viewToolbar frame]; |
| 1469 frame.origin.y = -frame.size.height; |
| 1470 int newToolbarHeight = 0; |
| 1471 |
| 1472 if (visible) { |
| 1473 frame.origin.y = 20; |
| 1474 newToolbarHeight = 40; |
| 1475 } |
| 1476 |
| 1477 _toolbarHeight = newToolbarHeight; |
| 1478 |
| 1479 [UIView animateWithDuration:0.5 |
| 1480 animations:^{ [_viewToolbar setFrame:frame]; } |
| 1481 completion:^(BOOL finished) { |
| 1482 // Nothing to do for now |
| 1483 }]; |
| 1484 |
| 1485 [self updateContentSize]; |
| 1486 [self panAndZoom:CGPointMake(0.0, 0.0) scaleBy:1.0]; |
| 1487 } |
| 1488 @end |
| OLD | NEW |