1/* 2 RDP Touch Pointer View 3 4 Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz 5 6 This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. 7 If a copy of the MPL was not distributed with this file, You can obtain one at 8 http://mozilla.org/MPL/2.0/. 9 */ 10 11#import "TouchPointerView.h" 12#import "Utils.h" 13 14#define RESET_DEFAULT_POINTER_IMAGE_DELAY 0.15 15 16#define POINTER_ACTION_CURSOR 0 17#define POINTER_ACTION_CLOSE 3 18#define POINTER_ACTION_RCLICK 2 19#define POINTER_ACTION_LCLICK 4 20#define POINTER_ACTION_MOVE 4 21#define POINTER_ACTION_SCROLL 5 22#define POINTER_ACTION_KEYBOARD 7 23#define POINTER_ACTION_EXTKEYBOARD 8 24#define POINTER_ACTION_RESET 6 25 26@interface TouchPointerView (Private) 27- (void)setCurrentPointerImage:(UIImage *)image; 28- (void)displayPointerActionImage:(UIImage *)image; 29- (BOOL)pointInsidePointer:(CGPoint)point; 30- (BOOL)pointInsidePointerArea:(int)area point:(CGPoint)point; 31- (CGPoint)getCursorPosition; 32- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event; 33- (void)handleSingleTap:(UITapGestureRecognizer *)gesture; 34- (void)handlerForGesture:(UIGestureRecognizer *)gesture sendClick:(BOOL)sendClick; 35@end 36 37@implementation TouchPointerView 38 39@synthesize delegate = _delegate; 40 41- (void)awakeFromNib 42{ 43 [super awakeFromNib]; 44 45 // set content mode when rotating (keep aspect ratio) 46 [self setContentMode:UIViewContentModeTopLeft]; 47 48 // load touchPointerImage 49 _default_pointer_img = [[UIImage 50 imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_default" 51 ofType:@"png"]] retain]; 52 _active_pointer_img = [[UIImage 53 imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_active" 54 ofType:@"png"]] retain]; 55 _lclick_pointer_img = [[UIImage 56 imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_lclick" 57 ofType:@"png"]] retain]; 58 _rclick_pointer_img = [[UIImage 59 imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_rclick" 60 ofType:@"png"]] retain]; 61 _scroll_pointer_img = [[UIImage 62 imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_scroll" 63 ofType:@"png"]] retain]; 64 _extkeyboard_pointer_img = [[UIImage 65 imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_ext_keyboard" 66 ofType:@"png"]] retain]; 67 _keyboard_pointer_img = [[UIImage 68 imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_keyboard" 69 ofType:@"png"]] retain]; 70 _reset_pointer_img = [[UIImage 71 imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_reset" 72 ofType:@"png"]] retain]; 73 _cur_pointer_img = _default_pointer_img; 74 _pointer_transformation = CGAffineTransformMake(1, 0, 0, 1, 0, 0); 75 76 // init flags 77 _pointer_moving = NO; 78 _pointer_scrolling = NO; 79 80 // create areas array 81 int i, j; 82 CGFloat area_width = [_cur_pointer_img size].width / 3.0f; 83 CGFloat area_height = [_cur_pointer_img size].height / 3.0f; 84 for (i = 0; i < 3; i++) 85 { 86 for (j = 0; j < 3; j++) 87 { 88 _pointer_areas[j + i * 3] = 89 CGRectMake(j * area_width, i * area_height, area_width, area_height); 90 } 91 } 92 93 // init gesture recognizers 94 UITapGestureRecognizer *singleTapRecognizer = 95 [[[UITapGestureRecognizer alloc] initWithTarget:self 96 action:@selector(handleSingleTap:)] autorelease]; 97 [singleTapRecognizer setNumberOfTouchesRequired:1]; 98 [singleTapRecognizer setNumberOfTapsRequired:1]; 99 100 UILongPressGestureRecognizer *dragDropRecognizer = [[[UILongPressGestureRecognizer alloc] 101 initWithTarget:self 102 action:@selector(handleDragDrop:)] autorelease]; 103 dragDropRecognizer.minimumPressDuration = 0.4; 104 // dragDropRecognizer.allowableMovement = 1000.0; 105 106 UILongPressGestureRecognizer *pointerMoveScrollRecognizer = 107 [[[UILongPressGestureRecognizer alloc] initWithTarget:self 108 action:@selector(handlePointerMoveScroll:)] 109 autorelease]; 110 pointerMoveScrollRecognizer.minimumPressDuration = 0.15; 111 pointerMoveScrollRecognizer.allowableMovement = 1000.0; 112 [pointerMoveScrollRecognizer requireGestureRecognizerToFail:dragDropRecognizer]; 113 114 [self addGestureRecognizer:singleTapRecognizer]; 115 [self addGestureRecognizer:dragDropRecognizer]; 116 [self addGestureRecognizer:pointerMoveScrollRecognizer]; 117} 118 119- (void)dealloc 120{ 121 [super dealloc]; 122 [_default_pointer_img autorelease]; 123 [_active_pointer_img autorelease]; 124 [_lclick_pointer_img autorelease]; 125 [_rclick_pointer_img autorelease]; 126 [_scroll_pointer_img autorelease]; 127 [_extkeyboard_pointer_img autorelease]; 128 [_keyboard_pointer_img autorelease]; 129 [_reset_pointer_img autorelease]; 130} 131 132#pragma mark - Public interface 133 134// positions the pointer on screen if it got offscreen after an orentation change 135- (void)ensurePointerIsVisible 136{ 137 CGRect bounds = [self bounds]; 138 if (_pointer_transformation.tx > (bounds.size.width - _cur_pointer_img.size.width)) 139 _pointer_transformation.tx = bounds.size.width - _cur_pointer_img.size.width; 140 if (_pointer_transformation.ty > (bounds.size.height - _cur_pointer_img.size.height)) 141 _pointer_transformation.ty = bounds.size.height - _cur_pointer_img.size.height; 142 [self setNeedsDisplay]; 143} 144 145// show/hides the touch pointer 146- (void)setHidden:(BOOL)hidden 147{ 148 [super setHidden:hidden]; 149 150 // if shown center pointer in view 151 if (!hidden) 152 { 153 _pointer_transformation = CGAffineTransformMakeTranslation( 154 ([self bounds].size.width - [_cur_pointer_img size].width) / 2, 155 ([self bounds].size.height - [_cur_pointer_img size].height) / 2); 156 [self setNeedsDisplay]; 157 } 158} 159 160- (UIEdgeInsets)getEdgeInsets 161{ 162 return UIEdgeInsetsMake(0, 0, [_cur_pointer_img size].width, [_cur_pointer_img size].height); 163} 164 165- (CGPoint)getPointerPosition 166{ 167 return CGPointMake(_pointer_transformation.tx, _pointer_transformation.ty); 168} 169 170- (int)getPointerWidth 171{ 172 return [_cur_pointer_img size].width; 173} 174 175- (int)getPointerHeight 176{ 177 return [_cur_pointer_img size].height; 178} 179 180@end 181 182@implementation TouchPointerView (Private) 183 184- (void)setCurrentPointerImage:(UIImage *)image 185{ 186 _cur_pointer_img = image; 187 [self setNeedsDisplay]; 188} 189 190- (void)displayPointerActionImage:(UIImage *)image 191{ 192 [self setCurrentPointerImage:image]; 193 [self performSelector:@selector(setCurrentPointerImage:) 194 withObject:_default_pointer_img 195 afterDelay:RESET_DEFAULT_POINTER_IMAGE_DELAY]; 196} 197 198// Only override drawRect: if you perform custom drawing. 199// An empty implementation adversely affects performance during animation. 200- (void)drawRect:(CGRect)rect 201{ 202 // Drawing code 203 CGContextRef context = UIGraphicsGetCurrentContext(); 204 CGContextSaveGState(context); 205 CGContextConcatCTM(context, _pointer_transformation); 206 CGContextDrawImage( 207 context, CGRectMake(0, 0, [_cur_pointer_img size].width, [_cur_pointer_img size].height), 208 [_cur_pointer_img CGImage]); 209 CGContextRestoreGState(context); 210} 211 212// helper that returns YES if the given point is within the pointer 213- (BOOL)pointInsidePointer:(CGPoint)point 214{ 215 CGRect rec = CGRectMake(0, 0, [_cur_pointer_img size].width, [_cur_pointer_img size].height); 216 return CGRectContainsPoint(CGRectApplyAffineTransform(rec, _pointer_transformation), point); 217} 218 219// helper that returns YES if the given point is within the given pointer area 220- (BOOL)pointInsidePointerArea:(int)area point:(CGPoint)point 221{ 222 CGRect rec = _pointer_areas[area]; 223 return CGRectContainsPoint(CGRectApplyAffineTransform(rec, _pointer_transformation), point); 224} 225 226// returns the position of the cursor 227- (CGPoint)getCursorPosition 228{ 229 CGRect transPointerArea = 230 CGRectApplyAffineTransform(_pointer_areas[POINTER_ACTION_CURSOR], _pointer_transformation); 231 return CGPointMake(CGRectGetMidX(transPointerArea), CGRectGetMidY(transPointerArea)); 232} 233 234// this filters events - if the pointer was clicked the scrollview won't get any events 235- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event 236{ 237 return [self pointInsidePointer:point]; 238} 239 240#pragma mark - Action handlers 241 242// handles single tap gestures, returns YES if the event was handled by the pointer, NO otherwise 243- (void)handleSingleTap:(UITapGestureRecognizer *)gesture 244{ 245 // get touch position within our view 246 CGPoint touchPos = [gesture locationInView:self]; 247 248 // look if pointer was in one of our action areas 249 if ([self pointInsidePointerArea:POINTER_ACTION_CLOSE point:touchPos]) 250 [[self delegate] touchPointerClose]; 251 else if ([self pointInsidePointerArea:POINTER_ACTION_LCLICK point:touchPos]) 252 { 253 [self displayPointerActionImage:_lclick_pointer_img]; 254 [[self delegate] touchPointerLeftClick:[self getCursorPosition] down:YES]; 255 [[self delegate] touchPointerLeftClick:[self getCursorPosition] down:NO]; 256 } 257 else if ([self pointInsidePointerArea:POINTER_ACTION_RCLICK point:touchPos]) 258 { 259 [self displayPointerActionImage:_rclick_pointer_img]; 260 [[self delegate] touchPointerRightClick:[self getCursorPosition] down:YES]; 261 [[self delegate] touchPointerRightClick:[self getCursorPosition] down:NO]; 262 } 263 else if ([self pointInsidePointerArea:POINTER_ACTION_KEYBOARD point:touchPos]) 264 { 265 [self displayPointerActionImage:_keyboard_pointer_img]; 266 [[self delegate] touchPointerToggleKeyboard]; 267 } 268 else if ([self pointInsidePointerArea:POINTER_ACTION_EXTKEYBOARD point:touchPos]) 269 { 270 [self displayPointerActionImage:_extkeyboard_pointer_img]; 271 [[self delegate] touchPointerToggleExtendedKeyboard]; 272 } 273 else if ([self pointInsidePointerArea:POINTER_ACTION_RESET point:touchPos]) 274 { 275 [self displayPointerActionImage:_reset_pointer_img]; 276 [[self delegate] touchPointerResetSessionView]; 277 } 278} 279 280- (void)handlerForGesture:(UIGestureRecognizer *)gesture sendClick:(BOOL)sendClick 281{ 282 if ([gesture state] == UIGestureRecognizerStateBegan) 283 { 284 CGPoint touchPos = [gesture locationInView:self]; 285 if ([self pointInsidePointerArea:POINTER_ACTION_LCLICK point:touchPos]) 286 { 287 _prev_touch_location = touchPos; 288 _pointer_moving = YES; 289 if (sendClick == YES) 290 { 291 [[self delegate] touchPointerLeftClick:[self getCursorPosition] down:YES]; 292 [self setCurrentPointerImage:_active_pointer_img]; 293 } 294 } 295 else if ([self pointInsidePointerArea:POINTER_ACTION_SCROLL point:touchPos]) 296 { 297 [self setCurrentPointerImage:_scroll_pointer_img]; 298 _prev_touch_location = touchPos; 299 _pointer_scrolling = YES; 300 } 301 } 302 else if ([gesture state] == UIGestureRecognizerStateChanged) 303 { 304 if (_pointer_moving) 305 { 306 CGPoint touchPos = [gesture locationInView:self]; 307 _pointer_transformation = CGAffineTransformTranslate( 308 _pointer_transformation, touchPos.x - _prev_touch_location.x, 309 touchPos.y - _prev_touch_location.y); 310 [[self delegate] touchPointerMove:[self getCursorPosition]]; 311 _prev_touch_location = touchPos; 312 [self setNeedsDisplay]; 313 } 314 else if (_pointer_scrolling) 315 { 316 CGPoint touchPos = [gesture locationInView:self]; 317 float delta = touchPos.y - _prev_touch_location.y; 318 if (delta > GetScrollGestureDelta()) 319 { 320 [[self delegate] touchPointerScrollDown:YES]; 321 _prev_touch_location = touchPos; 322 } 323 else if (delta < -GetScrollGestureDelta()) 324 { 325 [[self delegate] touchPointerScrollDown:NO]; 326 _prev_touch_location = touchPos; 327 } 328 } 329 } 330 else if ([gesture state] == UIGestureRecognizerStateEnded) 331 { 332 if (_pointer_moving) 333 { 334 if (sendClick == YES) 335 [[self delegate] touchPointerLeftClick:[self getCursorPosition] down:NO]; 336 _pointer_moving = NO; 337 [self setCurrentPointerImage:_default_pointer_img]; 338 } 339 340 if (_pointer_scrolling) 341 { 342 [self setCurrentPointerImage:_default_pointer_img]; 343 _pointer_scrolling = NO; 344 } 345 } 346} 347 348// handles long press gestures 349- (void)handleDragDrop:(UILongPressGestureRecognizer *)gesture 350{ 351 [self handlerForGesture:gesture sendClick:YES]; 352} 353 354- (void)handlePointerMoveScroll:(UILongPressGestureRecognizer *)gesture 355{ 356 [self handlerForGesture:gesture sendClick:NO]; 357} 358 359@end 360