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