FreeRDP/client/iOS/Views/TouchPointerView.m

360 lines
12 KiB
Mathematica
Raw Normal View History

/*
2019-11-06 17:24:51 +03:00
RDP Touch Pointer View
2013-12-04 14:37:57 +04:00
Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz
2019-11-06 17:24:51 +03:00
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file, You can obtain one at
http://mozilla.org/MPL/2.0/.
*/
#import "TouchPointerView.h"
#import "Utils.h"
#define RESET_DEFAULT_POINTER_IMAGE_DELAY 0.15
2019-11-06 17:24:51 +03:00
#define POINTER_ACTION_CURSOR 0
#define POINTER_ACTION_CLOSE 3
#define POINTER_ACTION_RCLICK 2
#define POINTER_ACTION_LCLICK 4
#define POINTER_ACTION_MOVE 4
#define POINTER_ACTION_SCROLL 5
#define POINTER_ACTION_KEYBOARD 7
#define POINTER_ACTION_EXTKEYBOARD 8
#define POINTER_ACTION_RESET 6
@interface TouchPointerView (Private)
2019-11-06 17:24:51 +03:00
- (void)setCurrentPointerImage:(UIImage *)image;
- (void)displayPointerActionImage:(UIImage *)image;
- (BOOL)pointInsidePointer:(CGPoint)point;
- (BOOL)pointInsidePointerArea:(int)area point:(CGPoint)point;
- (CGPoint)getCursorPosition;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
2019-11-06 17:24:51 +03:00
- (void)handleSingleTap:(UITapGestureRecognizer *)gesture;
- (void)handlerForGesture:(UIGestureRecognizer *)gesture sendClick:(BOOL)sendClick;
@end
@implementation TouchPointerView
@synthesize delegate = _delegate;
- (void)awakeFromNib
{
2019-11-06 17:24:51 +03:00
[super awakeFromNib];
// set content mode when rotating (keep aspect ratio)
[self setContentMode:UIViewContentModeTopLeft];
// load touchPointerImage
_default_pointer_img = [[UIImage
imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_default"
ofType:@"png"]] retain];
_active_pointer_img = [[UIImage
imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_active"
ofType:@"png"]] retain];
_lclick_pointer_img = [[UIImage
imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_lclick"
ofType:@"png"]] retain];
_rclick_pointer_img = [[UIImage
imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_rclick"
ofType:@"png"]] retain];
_scroll_pointer_img = [[UIImage
imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_scroll"
ofType:@"png"]] retain];
_extkeyboard_pointer_img = [[UIImage
imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_ext_keyboard"
ofType:@"png"]] retain];
_keyboard_pointer_img = [[UIImage
imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_keyboard"
ofType:@"png"]] retain];
_reset_pointer_img = [[UIImage
imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"touch_pointer_reset"
ofType:@"png"]] retain];
_cur_pointer_img = _default_pointer_img;
_pointer_transformation = CGAffineTransformMake(1, 0, 0, 1, 0, 0);
// init flags
_pointer_moving = NO;
_pointer_scrolling = NO;
// create areas array
int i, j;
CGFloat area_width = [_cur_pointer_img size].width / 3.0f;
CGFloat area_height = [_cur_pointer_img size].height / 3.0f;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3; j++)
{
_pointer_areas[j + i * 3] =
CGRectMake(j * area_width, i * area_height, area_width, area_height);
}
}
// init gesture recognizers
UITapGestureRecognizer *singleTapRecognizer =
[[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(handleSingleTap:)] autorelease];
[singleTapRecognizer setNumberOfTouchesRequired:1];
[singleTapRecognizer setNumberOfTapsRequired:1];
UILongPressGestureRecognizer *dragDropRecognizer = [[[UILongPressGestureRecognizer alloc]
initWithTarget:self
action:@selector(handleDragDrop:)] autorelease];
dragDropRecognizer.minimumPressDuration = 0.4;
// dragDropRecognizer.allowableMovement = 1000.0;
UILongPressGestureRecognizer *pointerMoveScrollRecognizer =
[[[UILongPressGestureRecognizer alloc] initWithTarget:self
action:@selector(handlePointerMoveScroll:)]
autorelease];
pointerMoveScrollRecognizer.minimumPressDuration = 0.15;
pointerMoveScrollRecognizer.allowableMovement = 1000.0;
[pointerMoveScrollRecognizer requireGestureRecognizerToFail:dragDropRecognizer];
[self addGestureRecognizer:singleTapRecognizer];
[self addGestureRecognizer:dragDropRecognizer];
[self addGestureRecognizer:pointerMoveScrollRecognizer];
}
- (void)dealloc
{
2019-11-06 17:24:51 +03:00
[super dealloc];
[_default_pointer_img autorelease];
[_active_pointer_img autorelease];
[_lclick_pointer_img autorelease];
[_rclick_pointer_img autorelease];
[_scroll_pointer_img autorelease];
[_extkeyboard_pointer_img autorelease];
[_keyboard_pointer_img autorelease];
[_reset_pointer_img autorelease];
}
#pragma mark - Public interface
// positions the pointer on screen if it got offscreen after an orentation change
2019-11-06 17:24:51 +03:00
- (void)ensurePointerIsVisible
{
2019-11-06 17:24:51 +03:00
CGRect bounds = [self bounds];
if (_pointer_transformation.tx > (bounds.size.width - _cur_pointer_img.size.width))
_pointer_transformation.tx = bounds.size.width - _cur_pointer_img.size.width;
if (_pointer_transformation.ty > (bounds.size.height - _cur_pointer_img.size.height))
_pointer_transformation.ty = bounds.size.height - _cur_pointer_img.size.height;
[self setNeedsDisplay];
}
// show/hides the touch pointer
2019-11-06 17:24:51 +03:00
- (void)setHidden:(BOOL)hidden
{
[super setHidden:hidden];
// if shown center pointer in view
if (!hidden)
{
_pointer_transformation = CGAffineTransformMakeTranslation(
([self bounds].size.width - [_cur_pointer_img size].width) / 2,
([self bounds].size.height - [_cur_pointer_img size].height) / 2);
[self setNeedsDisplay];
}
}
2019-11-06 17:24:51 +03:00
- (UIEdgeInsets)getEdgeInsets
{
2019-11-06 17:24:51 +03:00
return UIEdgeInsetsMake(0, 0, [_cur_pointer_img size].width, [_cur_pointer_img size].height);
}
- (CGPoint)getPointerPosition
{
2019-11-06 17:24:51 +03:00
return CGPointMake(_pointer_transformation.tx, _pointer_transformation.ty);
}
- (int)getPointerWidth
{
2019-11-06 17:24:51 +03:00
return [_cur_pointer_img size].width;
}
- (int)getPointerHeight
{
2019-11-06 17:24:51 +03:00
return [_cur_pointer_img size].height;
}
@end
@implementation TouchPointerView (Private)
2019-11-06 17:24:51 +03:00
- (void)setCurrentPointerImage:(UIImage *)image
{
2019-11-06 17:24:51 +03:00
_cur_pointer_img = image;
[self setNeedsDisplay];
}
2019-11-06 17:24:51 +03:00
- (void)displayPointerActionImage:(UIImage *)image
{
2019-11-06 17:24:51 +03:00
[self setCurrentPointerImage:image];
[self performSelector:@selector(setCurrentPointerImage:)
withObject:_default_pointer_img
afterDelay:RESET_DEFAULT_POINTER_IMAGE_DELAY];
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
2019-11-06 17:24:51 +03:00
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextConcatCTM(context, _pointer_transformation);
CGContextDrawImage(
context, CGRectMake(0, 0, [_cur_pointer_img size].width, [_cur_pointer_img size].height),
[_cur_pointer_img CGImage]);
CGContextRestoreGState(context);
}
// helper that returns YES if the given point is within the pointer
2019-11-06 17:24:51 +03:00
- (BOOL)pointInsidePointer:(CGPoint)point
{
2019-11-06 17:24:51 +03:00
CGRect rec = CGRectMake(0, 0, [_cur_pointer_img size].width, [_cur_pointer_img size].height);
return CGRectContainsPoint(CGRectApplyAffineTransform(rec, _pointer_transformation), point);
}
// helper that returns YES if the given point is within the given pointer area
2019-11-06 17:24:51 +03:00
- (BOOL)pointInsidePointerArea:(int)area point:(CGPoint)point
{
2019-11-06 17:24:51 +03:00
CGRect rec = _pointer_areas[area];
return CGRectContainsPoint(CGRectApplyAffineTransform(rec, _pointer_transformation), point);
}
// returns the position of the cursor
2019-11-06 17:24:51 +03:00
- (CGPoint)getCursorPosition
{
2019-11-06 17:24:51 +03:00
CGRect transPointerArea =
CGRectApplyAffineTransform(_pointer_areas[POINTER_ACTION_CURSOR], _pointer_transformation);
return CGPointMake(CGRectGetMidX(transPointerArea), CGRectGetMidY(transPointerArea));
}
// this filters events - if the pointer was clicked the scrollview won't get any events
2019-11-06 17:24:51 +03:00
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
2019-11-06 17:24:51 +03:00
return [self pointInsidePointer:point];
}
#pragma mark - Action handlers
// handles single tap gestures, returns YES if the event was handled by the pointer, NO otherwise
2019-11-06 17:24:51 +03:00
- (void)handleSingleTap:(UITapGestureRecognizer *)gesture
{
2019-11-06 17:24:51 +03:00
// get touch position within our view
CGPoint touchPos = [gesture locationInView:self];
// look if pointer was in one of our action areas
if ([self pointInsidePointerArea:POINTER_ACTION_CLOSE point:touchPos])
[[self delegate] touchPointerClose];
else if ([self pointInsidePointerArea:POINTER_ACTION_LCLICK point:touchPos])
{
[self displayPointerActionImage:_lclick_pointer_img];
[[self delegate] touchPointerLeftClick:[self getCursorPosition] down:YES];
[[self delegate] touchPointerLeftClick:[self getCursorPosition] down:NO];
}
else if ([self pointInsidePointerArea:POINTER_ACTION_RCLICK point:touchPos])
{
[self displayPointerActionImage:_rclick_pointer_img];
[[self delegate] touchPointerRightClick:[self getCursorPosition] down:YES];
[[self delegate] touchPointerRightClick:[self getCursorPosition] down:NO];
}
else if ([self pointInsidePointerArea:POINTER_ACTION_KEYBOARD point:touchPos])
{
[self displayPointerActionImage:_keyboard_pointer_img];
[[self delegate] touchPointerToggleKeyboard];
}
else if ([self pointInsidePointerArea:POINTER_ACTION_EXTKEYBOARD point:touchPos])
{
[self displayPointerActionImage:_extkeyboard_pointer_img];
[[self delegate] touchPointerToggleExtendedKeyboard];
}
else if ([self pointInsidePointerArea:POINTER_ACTION_RESET point:touchPos])
{
[self displayPointerActionImage:_reset_pointer_img];
[[self delegate] touchPointerResetSessionView];
}
}
2019-11-06 17:24:51 +03:00
- (void)handlerForGesture:(UIGestureRecognizer *)gesture sendClick:(BOOL)sendClick
{
2019-11-06 17:24:51 +03:00
if ([gesture state] == UIGestureRecognizerStateBegan)
{
CGPoint touchPos = [gesture locationInView:self];
if ([self pointInsidePointerArea:POINTER_ACTION_LCLICK point:touchPos])
{
_prev_touch_location = touchPos;
_pointer_moving = YES;
if (sendClick == YES)
{
[[self delegate] touchPointerLeftClick:[self getCursorPosition] down:YES];
[self setCurrentPointerImage:_active_pointer_img];
}
}
else if ([self pointInsidePointerArea:POINTER_ACTION_SCROLL point:touchPos])
{
[self setCurrentPointerImage:_scroll_pointer_img];
_prev_touch_location = touchPos;
_pointer_scrolling = YES;
}
}
else if ([gesture state] == UIGestureRecognizerStateChanged)
{
if (_pointer_moving)
{
CGPoint touchPos = [gesture locationInView:self];
_pointer_transformation = CGAffineTransformTranslate(
_pointer_transformation, touchPos.x - _prev_touch_location.x,
touchPos.y - _prev_touch_location.y);
[[self delegate] touchPointerMove:[self getCursorPosition]];
_prev_touch_location = touchPos;
[self setNeedsDisplay];
}
else if (_pointer_scrolling)
{
CGPoint touchPos = [gesture locationInView:self];
float delta = touchPos.y - _prev_touch_location.y;
if (delta > GetScrollGestureDelta())
{
[[self delegate] touchPointerScrollDown:YES];
_prev_touch_location = touchPos;
}
else if (delta < -GetScrollGestureDelta())
{
[[self delegate] touchPointerScrollDown:NO];
_prev_touch_location = touchPos;
}
}
}
else if ([gesture state] == UIGestureRecognizerStateEnded)
{
if (_pointer_moving)
{
if (sendClick == YES)
[[self delegate] touchPointerLeftClick:[self getCursorPosition] down:NO];
_pointer_moving = NO;
[self setCurrentPointerImage:_default_pointer_img];
}
if (_pointer_scrolling)
{
[self setCurrentPointerImage:_default_pointer_img];
_pointer_scrolling = NO;
}
}
}
// handles long press gestures
2019-11-06 17:24:51 +03:00
- (void)handleDragDrop:(UILongPressGestureRecognizer *)gesture
{
2019-11-06 17:24:51 +03:00
[self handlerForGesture:gesture sendClick:YES];
}
2019-11-06 17:24:51 +03:00
- (void)handlePointerMoveScroll:(UILongPressGestureRecognizer *)gesture
{
2019-11-06 17:24:51 +03:00
[self handlerForGesture:gesture sendClick:NO];
}
@end