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

Side by Side Diff: remoting/ios/ui/host_view_controller.mm

Issue 186733007: iOS Chromoting Client (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698