SameBoy | Accurate GB/GBC emulator |
| download: https://git.y1.nz/archives/sameboy.tar.gz | |
| README | Files | Log | Refs | LICENSE |
Cocoa/GBJoyConManager.m
1 #import "GBJoyConManager.h"
2 #import "GBTintedImageCell.h"
3 #import <objc/runtime.h>
4
5 @implementation GBJoyConManager
6 {
7 GBTintedImageCell *_tintedImageCell;
8 NSImageCell *_imageCell;
9 NSMutableDictionary<NSString *, NSString *> *_pairings;
10 NSMutableDictionary<NSString *, NSNumber *> *_gripSettings;
11 bool _unpairing;
12 }
13
14 + (instancetype)sharedInstance
15 {
16 static GBJoyConManager *manager = nil;
17 static dispatch_once_t onceToken;
18 dispatch_once(&onceToken, ^{
19 manager = [[super allocWithZone:nil] _init];
20 });
21 return manager;
22 }
23
24 - (NSArray <JOYController *> *)joycons
25 {
26 NSMutableArray *ret = [[JOYController allControllers] mutableCopy];
27 for (JOYController *controller in [JOYController allControllers]) {
28 if (controller.joyconType == JOYJoyConTypeNone) {
29 [ret removeObject:controller];
30 }
31 }
32 return ret;
33 }
34
35 + (instancetype)allocWithZone:(struct _NSZone *)zone
36 {
37 return [self sharedInstance];
38 }
39
40 + (instancetype)alloc
41 {
42 return [self sharedInstance];
43 }
44
45 - (instancetype)init
46 {
47 return [self.class sharedInstance];
48 }
49
50 - (instancetype) _init
51 {
52 self = [super init];
53 _imageCell = [[NSImageCell alloc] init];
54 _tintedImageCell = [[GBTintedImageCell alloc] init];
55 if (@available(macOS 10.14, *)) {
56 _tintedImageCell.tint = [NSColor controlAccentColor];
57 }
58 else {
59 _tintedImageCell.tint = [NSColor selectedMenuItemColor];
60 }
61 _pairings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoyConPairings"] ?: @{} mutableCopy];
62 _gripSettings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoyConGrips"] ?: @{} mutableCopy];
63
64 // Sanity check the pairings
65 for (NSString *key in _pairings) {
66 if (![_pairings[_pairings[key]] isEqualToString:key]) {
67 [_pairings removeAllObjects];
68 break;
69 }
70 }
71
72 [JOYController registerListener:self];
73 for (JOYController *controller in [JOYController allControllers]) {
74 [self controllerConnected:controller];
75 }
76
77 return self;
78 }
79
80 - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
81 {
82 return self.joycons.count;
83 }
84
85 - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
86 {
87 if (row >= [self numberOfRowsInTableView:tableView]) return nil;
88
89 unsigned columnIndex = [[tableView tableColumns] indexOfObject:tableColumn];
90 JOYController *controller = self.joycons[row];
91 switch (columnIndex) {
92 case 0: {
93 switch (controller.joyconType) {
94 case JOYJoyConTypeNone:
95 return nil;
96 case JOYJoyConTypeLeft:
97 return [NSImage imageNamed:[NSString stringWithFormat:@"%sJoyConLeftTemplate", controller.usesHorizontalJoyConGrip? "Horizontal" :""]];
98 case JOYJoyConTypeRight:
99 return [NSImage imageNamed:[NSString stringWithFormat:@"%sJoyConRightTemplate", controller.usesHorizontalJoyConGrip? "Horizontal" :""]];
100 case JOYJoyConTypeDual:
101 return [NSImage imageNamed:@"JoyConDualTemplate"];
102 }
103 }
104 case 1: {
105 NSMutableAttributedString *ret = [[NSMutableAttributedString alloc] initWithString:controller.deviceName
106 attributes:@{NSFontAttributeName:
107 [NSFont systemFontOfSize:[NSFont systemFontSize]]}];
108
109 [ret appendAttributedString:[[NSAttributedString alloc] initWithString:[@"\n" stringByAppendingString:controller.uniqueID]
110 attributes:@{NSFontAttributeName:
111 [NSFont systemFontOfSize:[NSFont smallSystemFontSize]],
112 NSForegroundColorAttributeName:[NSColor disabledControlTextColor]}]];
113 return ret;
114 }
115 case 2:
116 return @([(_gripSettings[controller.uniqueID] ?: @(-1)) unsignedIntValue] + 1);
117 }
118 return nil;
119 }
120
121 - (void)updateGripForController:(JOYController *)controller
122 {
123 NSNumber *grip = _gripSettings[controller.uniqueID];
124 if (!grip) {
125 controller.usesHorizontalJoyConGrip = [[NSUserDefaults standardUserDefaults] boolForKey:@"GBJoyConsDefaultsToHorizontal"];
126 return;
127 }
128 controller.usesHorizontalJoyConGrip = [grip unsignedIntValue] == 1;
129 }
130
131 - (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
132 {
133 unsigned columnIndex = [[tableView tableColumns] indexOfObject:tableColumn];
134 if (columnIndex != 2) return;
135 if (row >= [self numberOfRowsInTableView:tableView]) return;
136 JOYController *controller = self.joycons[row];
137 if (controller.joyconType == JOYJoyConTypeDual) {
138 return;
139 }
140 switch ([object unsignedIntValue]) {
141 case 0:
142 [_gripSettings removeObjectForKey:controller.uniqueID];
143 break;
144 case 1:
145 _gripSettings[controller.uniqueID] = @(0);
146 break;
147 case 2:
148 _gripSettings[controller.uniqueID] = @(1);
149 break;
150 }
151 [[NSUserDefaults standardUserDefaults] setObject:_gripSettings forKey:@"GBJoyConGrips"];
152 [self updateGripForController:controller];
153 }
154
155 - (NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
156 {
157 if (row >= [self numberOfRowsInTableView:tableView]) return [[NSCell alloc] init];
158
159 unsigned columnIndex = [[tableView tableColumns] indexOfObject:tableColumn];
160 if (columnIndex == 2) {
161 JOYCombinedController *controller = (JOYCombinedController *)self.joycons[row];
162 if (controller.joyconType == JOYJoyConTypeDual) {
163 NSButtonCell *cell = [[NSButtonCell alloc] initTextCell:@"Separate Joy-Cons"];
164 cell.bezelStyle = NSBezelStyleRounded;
165 cell.action = @selector(invoke);
166 id block = ^(void) {
167 dispatch_async(dispatch_get_main_queue(), ^{
168 for (JOYController *child in controller.children) {
169 [_pairings removeObjectForKey:child.uniqueID];
170 }
171 [[NSUserDefaults standardUserDefaults] setObject:_pairings forKey:@"GBJoyConPairings"];
172 _unpairing = true;
173 [controller breakApart];
174 _unpairing = false;
175 });
176 };
177 // To retain the block
178 objc_setAssociatedObject(cell, @selector(breakApart), block, OBJC_ASSOCIATION_RETAIN);
179 cell.target = block;
180 return cell;
181 }
182 }
183 if (columnIndex == 0) {
184 JOYController *controller = self.joycons[row];
185 for (JOYButton *button in controller.buttons) {
186 if (button.isPressed) {
187 return _tintedImageCell;
188 }
189 }
190 return _imageCell;
191 }
192 return nil;
193 }
194
195 - (void)controllerConnected:(JOYController *)controller
196 {
197 [self updateGripForController:controller];
198 for (JOYController *partner in [JOYController allControllers]) {
199 if ([partner.uniqueID isEqualToString:_pairings[controller.uniqueID]]) {
200 [self pairJoyCon:controller withJoyCon:partner];
201 break;
202 }
203 }
204 if (controller.joyconType == JOYJoyConTypeLeft || controller.joyconType == JOYJoyConTypeRight) {
205 [self autopair:nil];
206 }
207 if (_arrangementMode) {
208 [self.tableView reloadData];
209 }
210 }
211
212 - (IBAction)autopair:(id)sender
213 {
214 if (_unpairing) return;
215 if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBJoyConAutoPair"]) return;
216 NSArray<JOYController *> *controllers = [[JOYController allControllers] copy];
217 for (JOYController *first in controllers) {
218 if (_pairings[first.uniqueID]) continue; // Has an established partner
219 if (first.joyconType != JOYJoyConTypeLeft) continue;
220 for (JOYController *second in controllers) {
221 if (_pairings[second.uniqueID]) continue; // Has an established partner
222 if (second.joyconType != JOYJoyConTypeRight) continue;
223 [self pairJoyCon:first withJoyCon:second];
224 break;
225 }
226 }
227 if (_arrangementMode) {
228 [self.tableView reloadData];
229 }
230 }
231
232 - (void)controllerDisconnected:(JOYController *)controller
233 {
234 if (_arrangementMode) {
235 [self.tableView reloadData];
236 }
237 }
238
239 - (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
240 {
241 unsigned columnIndex = [[tableView tableColumns] indexOfObject:tableColumn];
242 return columnIndex == 2;
243 }
244
245 - (JOYCombinedController *)pairJoyCon:(JOYController *)first withJoyCon:(JOYController *)second
246 {
247 if (first.joyconType != JOYJoyConTypeLeft && first.joyconType != JOYJoyConTypeRight) return nil; // Not a Joy-Con
248 if (second.joyconType != JOYJoyConTypeLeft && second.joyconType != JOYJoyConTypeRight) return nil; // Not a Joy-Con
249 if (first.joyconType == second.joyconType) return nil; // Not a sensible pair
250
251 _pairings[first.uniqueID] = second.uniqueID;
252 _pairings[second.uniqueID] = first.uniqueID;
253 first.usesHorizontalJoyConGrip = false;
254 second.usesHorizontalJoyConGrip = false;
255 [[NSUserDefaults standardUserDefaults] setObject:_pairings forKey:@"GBJoyConPairings"];
256 return [[JOYCombinedController alloc] initWithChildren:@[first, second]];
257 }
258
259 - (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button
260 {
261 if (!_arrangementMode) return;
262 if (controller.joyconType == JOYJoyConTypeNone) return;
263 [self.tableView setNeedsDisplay:true];
264 if (controller.joyconType != JOYJoyConTypeLeft && controller.joyconType != JOYJoyConTypeRight) return;
265 if (button.usage != JOYButtonUsageL1 && button.usage != JOYButtonUsageR1) return;
266
267
268 // L or R were pressed on a single Joy-Con, try and pair available Joy-Cons
269 JOYController *left = nil;
270 JOYController *right = nil;
271 for (JOYController *controller in [JOYController allControllers]) {
272 if (!left && controller.joyconType == JOYJoyConTypeLeft) {
273 for (JOYButton *button in controller.buttons) {
274 if (button.usage == JOYButtonUsageL1 && button.isPressed) {
275 left = controller;
276 break;
277 }
278 }
279 }
280 if (!right && controller.joyconType == JOYJoyConTypeRight) {
281 for (JOYButton *button in controller.buttons) {
282 if (button.usage == JOYButtonUsageR1 && button.isPressed) {
283 right = controller;
284 break;
285 }
286 }
287 }
288 if (left && right) {
289 [self pairJoyCon:left withJoyCon:right];
290 return;
291 }
292 }
293 }
294
295 - (IBAction)toggleHorizontalDefault:(NSButton *)sender
296 {
297 for (JOYController *controller in self.joycons) {
298 [self updateGripForController:controller];
299 }
300 if (_arrangementMode) {
301 [self.tableView reloadData];
302 }
303 }
304
305 - (void)setArrangementMode:(bool)arrangementMode
306 {
307 _arrangementMode = arrangementMode;
308 if (arrangementMode) {
309 [self.tableView reloadData];
310 }
311 }
312
313 @end
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.