1/* Copyright (C) 2010, 2011  Chaoji Li */
2
3#import "GamePadView.h"
4
5
6#define CENTER_OF_RECT(r) CGPointMake(r.size.width/2,r.size.height/2)
7#define DISTANCE_BETWEEN(a,b) sqrt((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y))
8
9#define CHOP(x) \
10if ((x) > 1.0) { \
11	(x) = 1.0; \
12} else if ((x) < -1.0) { \
13	(x) = -1.0; \
14} else {}
15
16/** DPadView */
17@implementation DPadView
18@synthesize backgroundImage;
19@synthesize images;
20
21- (id)initWithFrame:(CGRect)frame {
22	if ((self = [super initWithFrame:frame])) {
23		self.backgroundColor = [UIColor clearColor];
24		self.backgroundImage = [UIImage imageNamed:@"joythumb-glass.png"];
25
26#if SDL_VERSION_ATLEAST(2,0,13)
27		int vjoy_index = SDL_JoystickAttachVirtual(
28			SDL_JOYSTICK_TYPE_GAMECONTROLLER,
29			SDL_CONTROLLER_AXIS_MAX,
30			SDL_CONTROLLER_BUTTON_MAX,
31			0
32		);
33		if (vjoy_index < 0) {
34			printf("SDL_JoystickAttachVirtual failed: %s\n", SDL_GetError());
35		} else {
36			vjoy_controller = SDL_GameControllerOpen(vjoy_index);
37			if (!vjoy_controller) {
38				printf("SDL_GameControllerOpen failed for virtual joystick: %s\n", SDL_GetError());
39				SDL_JoystickDetachVirtual(vjoy_index);
40			}
41		}
42		[self reset];
43		// printf("VJOY INIT, controller=%p\n", vjoy_controller);
44#endif
45	}
46	return self;
47}
48
49- (void)drawRect:(CGRect)rect
50{
51	if (backgroundImage) {
52		[backgroundImage drawInRect:rect];
53	}
54}
55
56- (void)dealloc {
57#if SDL_VERSION_ATLEAST(2,0,13)
58	if (vjoy_controller) {
59		const SDL_JoystickID vjoy_controller_id = SDL_JoystickInstanceID(
60			SDL_GameControllerGetJoystick(vjoy_controller)
61		);
62		SDL_GameControllerClose(vjoy_controller);
63		for (int i = 0, n = SDL_NumJoysticks(); i < n; ++i) {
64			const SDL_JoystickID current_id = SDL_JoystickGetDeviceInstanceID(i);
65			if (current_id == vjoy_controller_id) {
66				// printf("detach virtual at id:%d, index:%d\n", current_id, i);
67				SDL_JoystickDetachVirtual(i);
68				break;
69			}
70		}
71	}
72#endif
73
74	[backgroundImage release];
75	[images release];
76	[super dealloc];
77}
78
79- (void)reset
80{
81	vjoy_is_active = false;
82	SDL_JoystickSetVirtualAxis(
83		SDL_GameControllerGetJoystick(vjoy_controller),
84		SDL_CONTROLLER_AXIS_LEFTX,
85		0
86	);
87	SDL_JoystickSetVirtualAxis(
88		SDL_GameControllerGetJoystick(vjoy_controller),
89		SDL_CONTROLLER_AXIS_LEFTY,
90		0
91	);
92	vjoy_center = vjoy_current = CGPointMake(0,0);
93	vjoy_input_source = nil;
94	[self updateViewTransform];
95}
96
97- (void)updateViewTransform
98{
99	if (!vjoy_is_active) {
100		self.transform = CGAffineTransformIdentity;
101//        printf("updateViewTransform: reset to identity\n");
102	} else {
103
104		// Calculate the position of vjoy_center within this view's
105		// parent/super-view.  This requires first resetting this view's
106		// UIKit 'transform' to an untransformed state, as UIView's
107		// method, 'convertPoint:toView:', will apply any existing
108		// transform.
109		self.transform = CGAffineTransformIdentity;
110		CGPoint vjoy_center_in_parent_view = [self convertPoint:vjoy_center toView:self.superview];
111		const CGPoint translation = CGPointMake(
112			vjoy_center_in_parent_view.x - self.center.x,
113			vjoy_center_in_parent_view.y - self.center.y
114		);
115		self.transform = CGAffineTransformMakeTranslation(translation.x, translation.y);
116	}
117	[self setNeedsDisplay];
118}
119
120
121- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
122{
123	UITouch *touch = [touches anyObject];
124	// printf("touchesBegan, %p\n", touch);
125
126#if SDL_VERSION_ATLEAST(2,0,13)
127	if (!vjoy_is_active && touch != nil) {
128		vjoy_input_source = touch;
129		vjoy_center = vjoy_current = [touch locationInView:self];
130		vjoy_is_active = true;
131		SDL_JoystickSetVirtualAxis(
132			SDL_GameControllerGetJoystick(vjoy_controller),
133			SDL_CONTROLLER_AXIS_LEFTX,
134			0
135		);
136		SDL_JoystickSetVirtualAxis(
137			SDL_GameControllerGetJoystick(vjoy_controller),
138			SDL_CONTROLLER_AXIS_LEFTY,
139			0
140		);
141		[self updateViewTransform];
142		// printf("VJOY START\n");
143	}
144#endif
145}
146
147- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
148{
149//    printf("touchesMoved, vjoy_input_source:%p, touch:%p\n", vjoy_input_source, touch);
150
151#if SDL_VERSION_ATLEAST(2,0,13)
152	UITouch * __strong current_input_source = vjoy_input_source;
153	if (vjoy_is_active && [touches containsObject:current_input_source]) {
154
155		// Calculate new vjoy_current, but first, reset this view's
156		// UIKit-transform, lest the call to UITouch's 'locationInView:'
157		// method reports incorrect values (due to a previously-applied
158		// transform).
159		self.transform = CGAffineTransformIdentity;
160		vjoy_current = [current_input_source locationInView:self];
161
162		float dx = vjoy_current.x - vjoy_center.x;
163		float dy = vjoy_current.y - vjoy_center.y;
164
165		// Move the vjoy's center if it's outside of its radius
166		float dlength = sqrt((dx * dx) + (dy * dy));
167		if (dlength > vjoy_radius) {
168			vjoy_center.x = vjoy_current.x - (dx * (vjoy_radius / dlength));
169			vjoy_center.y = vjoy_current.y - (dy * (vjoy_radius / dlength));
170			dx = vjoy_current.x - vjoy_center.x;
171			dy = vjoy_current.y - vjoy_center.y;
172		}
173
174		// Update vjoy state
175		const Sint16 joy_axis_x_raw = (Sint16)((dx / vjoy_radius) * SDL_JOYSTICK_AXIS_MAX);
176		SDL_JoystickSetVirtualAxis(
177			SDL_GameControllerGetJoystick(vjoy_controller),
178			SDL_CONTROLLER_AXIS_LEFTX,
179			joy_axis_x_raw
180		);
181		const Sint16 joy_axis_y_raw = (Sint16)((dy / vjoy_radius) * SDL_JOYSTICK_AXIS_MAX);
182		SDL_JoystickSetVirtualAxis(
183			SDL_GameControllerGetJoystick(vjoy_controller),
184			SDL_CONTROLLER_AXIS_LEFTY,
185			joy_axis_y_raw
186		);
187
188		// Update visuals
189		[self updateViewTransform];
190
191		// printf("VJOY MOVE: %d, %d\n", (int)joy_axis_x_raw, (int)joy_axis_y_raw);
192	}
193#endif
194}
195
196- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
197{
198	// printf("touchesEnded, vjoy_input_source:%p, touch:%p\n", vjoy_input_source, touch);
199
200#if SDL_VERSION_ATLEAST(2,0,13)
201	if ([touches containsObject:vjoy_input_source]) {
202		// Mark vjoy as inactive
203		[self reset];
204	}
205#endif
206}
207
208- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
209{
210	CGPoint ptCenter = CENTER_OF_RECT(self.bounds);
211	return DISTANCE_BETWEEN(point, ptCenter) < self.bounds.size.width/2;
212}
213
214@end
215
216/** GamePadButton */
217@implementation GamePadButton
218@synthesize pressed;
219@synthesize keyCodes;
220@synthesize style;
221@synthesize title;
222@synthesize images;
223@synthesize textColor;
224@synthesize delegate;
225
226- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
227{
228	if (style == GamePadButtonStyleCircle) {
229		CGPoint ptCenter = CENTER_OF_RECT(self.bounds);
230		return DISTANCE_BETWEEN(point, ptCenter) < self.bounds.size.width/2;
231	} else {
232		return [super pointInside:point withEvent:event];
233	}
234}
235
236- (void)setTitle:(NSString *)s
237{
238	if (title) [title release];
239	title = s;
240	[title retain];
241	[self setNeedsDisplay];
242}
243
244- (void)setPressed:(BOOL)b
245{
246	if (pressed != b) {
247		pressed = b;
248		if (delegate) {
249			if (b)
250				[delegate buttonDown:self];
251			else
252				[delegate buttonUp:self];
253		}
254		[self setNeedsDisplay];
255	}
256}
257
258- (void)setStyle:(GamePadButtonStyle)s
259{
260	style = s;
261	[self setNeedsDisplay];
262}
263
264- (id)initWithFrame:(CGRect)frame {
265	if ((self = [super initWithFrame:frame])) {
266		self.backgroundColor = [UIColor clearColor];
267		self.textColor       = [UIColor colorWithRed:0 green:0 blue:0 alpha:1];
268		keyCodes = [[NSMutableArray alloc] initWithCapacity:2];
269	}
270	return self;
271}
272
273- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
274{
275	self.pressed = YES;
276}
277
278- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
279{
280	self.pressed = NO;
281//	[[self superview] touchesEnded:touches withEvent:event];
282}
283
284-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
285{
286//	[[self superview] touchesMoved:touches withEvent:event];
287}
288
289- (void)drawRect:(CGRect)rect
290{
291	int bkgIndex;
292	UIColor *color = nil;
293
294	if (!pressed) {
295		color = [textColor colorWithAlphaComponent:0.3];
296		bkgIndex = 0;
297	} else {
298		color = [textColor colorWithAlphaComponent:0.8];
299		bkgIndex = 1;
300	}
301
302	if ([images count] > bkgIndex) {
303		UIImage *image = [images objectAtIndex:bkgIndex];
304		[image drawInRect:rect];
305	}
306
307	if (title) {
308		float fontSize = MIN(14, rect.size.height/4);
309		UIFont *fnt = [UIFont systemFontOfSize:fontSize];
310		CGSize size = [title sizeWithAttributes:@{NSFontAttributeName:fnt}];
311		CGRect rc = CGRectMake((rect.size.width-size.width)/2,
312					(rect.size.height-size.height)/2,
313					size.width, size.height);
314		[color setFill];
315		[title drawInRect:rc withAttributes:@{NSFontAttributeName:fnt,
316											  NSForegroundColorAttributeName: color}];
317	}
318}
319
320- (void)dealloc {
321	[images release];
322	[title release];
323	[textColor release];
324	[keyCodes release];
325	[super dealloc];
326}
327
328@end
329/** End */
330