SameBoy/Cocoa/GBJoyConManager.m

324 lines
12 KiB
Objective-C

#import "GBJoyConManager.h"
#import "GBTintedImageCell.h"
#import <objc/runtime.h>
@implementation GBJoyConManager
{
GBTintedImageCell *_tintedImageCell;
NSImageCell *_imageCell;
NSMutableDictionary<NSString *, NSString *> *_pairings;
NSMutableDictionary<NSString *, NSNumber *> *_gripSettings;
NSButton *_autoPairCheckbox;
bool _unpairing;
}
+ (instancetype)sharedInstance
{
static GBJoyConManager *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[self alloc] _init];
});
return manager;
}
- (NSArray <JOYController *> *)joycons
{
NSMutableArray *ret = [[JOYController allControllers] mutableCopy];
for (JOYController *controller in [JOYController allControllers]) {
if (controller.joyconType == JOYJoyConTypeNone) {
[ret removeObject:controller];
}
}
return ret;
}
- (instancetype)init
{
return [self.class sharedInstance];
}
- (instancetype) _init
{
self = [super init];
_imageCell = [[NSImageCell alloc] init];
_tintedImageCell = [[GBTintedImageCell alloc] init];
if (@available(macOS 10.14, *)) {
_tintedImageCell.tint = [NSColor controlAccentColor];
}
else {
_tintedImageCell.tint = [NSColor selectedMenuItemColor];
}
_pairings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoyConPairings"] ?: @{} mutableCopy];
_gripSettings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoyConGrips"] ?: @{} mutableCopy];
// Sanity check the pairings
for (NSString *key in _pairings) {
if (![_pairings[_pairings[key]] isEqualToString:key]) {
[_pairings removeAllObjects];
break;
}
}
[JOYController registerListener:self];
for (JOYController *controller in [JOYController allControllers]) {
[self controllerConnected:controller];
}
return self;
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
return self.joycons.count;
}
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
if (row >= [self numberOfRowsInTableView:tableView]) return nil;
unsigned columnIndex = [[tableView tableColumns] indexOfObject:tableColumn];
JOYController *controller = self.joycons[row];
switch (columnIndex) {
case 0: {
switch (controller.joyconType) {
case JOYJoyConTypeNone:
return nil;
case JOYJoyConTypeLeft:
return [NSImage imageNamed:[NSString stringWithFormat:@"%sJoyConLeftTemplate", controller.usesHorizontalJoyConGrip? "Horizontal" :""]];
case JOYJoyConTypeRight:
return [NSImage imageNamed:[NSString stringWithFormat:@"%sJoyConRightTemplate", controller.usesHorizontalJoyConGrip? "Horizontal" :""]];
case JOYJoyConTypeDual:
return [NSImage imageNamed:@"JoyConDualTemplate"];
}
}
case 1: {
NSMutableAttributedString *ret = [[NSMutableAttributedString alloc] initWithString:controller.deviceName
attributes:@{NSFontAttributeName:
[NSFont systemFontOfSize:[NSFont systemFontSize]]}];
[ret appendAttributedString:[[NSAttributedString alloc] initWithString:[@"\n" stringByAppendingString:controller.uniqueID]
attributes:@{NSFontAttributeName:
[NSFont systemFontOfSize:[NSFont smallSystemFontSize]],
NSForegroundColorAttributeName:[NSColor disabledControlTextColor]}]];
return ret;
}
case 2:
return @([(_gripSettings[controller.uniqueID] ?: @(-1)) unsignedIntValue] + 1);
}
return nil;
}
- (void)updateGripForController:(JOYController *)controller
{
NSNumber *grip = _gripSettings[controller.uniqueID];
if (!grip) {
controller.usesHorizontalJoyConGrip = [[NSUserDefaults standardUserDefaults] boolForKey:@"GBJoyConsDefaultsToHorizontal"];
return;
}
controller.usesHorizontalJoyConGrip = [grip unsignedIntValue] == 1;
}
- (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
unsigned columnIndex = [[tableView tableColumns] indexOfObject:tableColumn];
if (columnIndex != 2) return;
if (row >= [self numberOfRowsInTableView:tableView]) return;
JOYController *controller = self.joycons[row];
if (controller.joyconType == JOYJoyConTypeDual) {
return;
}
switch ([object unsignedIntValue]) {
case 0:
[_gripSettings removeObjectForKey:controller.uniqueID];
break;
case 1:
_gripSettings[controller.uniqueID] = @(0);
break;
case 2:
_gripSettings[controller.uniqueID] = @(1);
break;
}
[[NSUserDefaults standardUserDefaults] setObject:_gripSettings forKey:@"GBJoyConGrips"];
[self updateGripForController:controller];
}
- (NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
if (row >= [self numberOfRowsInTableView:tableView]) return [[NSCell alloc] init];
unsigned columnIndex = [[tableView tableColumns] indexOfObject:tableColumn];
if (columnIndex == 2) {
JOYCombinedController *controller = (JOYCombinedController *)self.joycons[row];
if (controller.joyconType == JOYJoyConTypeDual) {
NSButtonCell *cell = [[NSButtonCell alloc] initTextCell:@"Separate Joy-Cons"];
cell.bezelStyle = NSBezelStyleRounded;
cell.action = @selector(invoke);
id block = ^(void) {
dispatch_async(dispatch_get_main_queue(), ^{
for (JOYController *child in controller.children) {
[_pairings removeObjectForKey:child.uniqueID];
}
[[NSUserDefaults standardUserDefaults] setObject:_pairings forKey:@"GBJoyConPairings"];
_unpairing = true;
[controller breakApart];
_unpairing = false;
});
};
// To retain the block
objc_setAssociatedObject(cell, @selector(breakApart), block, OBJC_ASSOCIATION_RETAIN);
cell.target = block;
return cell;
}
}
if (columnIndex == 0) {
JOYController *controller = self.joycons[row];
for (JOYButton *button in controller.buttons) {
if (button.isPressed) {
return _tintedImageCell;
}
}
return _imageCell;
}
return nil;
}
- (void)controllerConnected:(JOYController *)controller
{
[self updateGripForController:controller];
for (JOYController *partner in [JOYController allControllers]) {
if ([partner.uniqueID isEqualToString:_pairings[controller.uniqueID]]) {
[self pairJoyCon:controller withJoyCon:partner];
break;
}
}
if (controller.joyconType == JOYJoyConTypeLeft || controller.joyconType == JOYJoyConTypeRight) {
[self autopair];
}
if (_arrangementMode) {
[self.tableView reloadData];
}
}
- (void)autopair
{
if (_unpairing) return;
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBJoyConAutoPair"]) return;
NSArray<JOYController *> *controllers = [[JOYController allControllers] copy];
for (JOYController *first in controllers) {
if (_pairings[first.uniqueID]) continue; // Has an established partner
if (first.joyconType != JOYJoyConTypeLeft) continue;
for (JOYController *second in controllers) {
if (_pairings[second.uniqueID]) continue; // Has an established partner
if (second.joyconType != JOYJoyConTypeRight) continue;
[self pairJoyCon:first withJoyCon:second];
break;
}
}
if (_arrangementMode) {
[self.tableView reloadData];
}
}
- (void)controllerDisconnected:(JOYController *)controller
{
if (_arrangementMode) {
[self.tableView reloadData];
}
}
- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
unsigned columnIndex = [[tableView tableColumns] indexOfObject:tableColumn];
return columnIndex == 2;
}
- (JOYCombinedController *)pairJoyCon:(JOYController *)first withJoyCon:(JOYController *)second
{
if (first.joyconType != JOYJoyConTypeLeft && first.joyconType != JOYJoyConTypeRight) return nil; // Not a Joy-Con
if (second.joyconType != JOYJoyConTypeLeft && second.joyconType != JOYJoyConTypeRight) return nil; // Not a Joy-Con
if (first.joyconType == second.joyconType) return nil; // Not a sensible pair
_pairings[first.uniqueID] = second.uniqueID;
_pairings[second.uniqueID] = first.uniqueID;
first.usesHorizontalJoyConGrip = false;
second.usesHorizontalJoyConGrip = false;
[[NSUserDefaults standardUserDefaults] setObject:_pairings forKey:@"GBJoyConPairings"];
return [[JOYCombinedController alloc] initWithChildren:@[first, second]];
}
- (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button
{
if (!_arrangementMode) return;
if (controller.joyconType == JOYJoyConTypeNone) return;
[self.tableView setNeedsDisplay:true];
if (controller.joyconType != JOYJoyConTypeLeft && controller.joyconType != JOYJoyConTypeRight) return;
if (button.usage != JOYButtonUsageL1 && button.usage != JOYButtonUsageR1) return;
// L or R were pressed on a single Joy-Con, try and pair available Joy-Cons
JOYController *left = nil;
JOYController *right = nil;
for (JOYController *controller in [JOYController allControllers]) {
if (!left && controller.joyconType == JOYJoyConTypeLeft) {
for (JOYButton *button in controller.buttons) {
if (button.usage == JOYButtonUsageL1 && button.isPressed) {
left = controller;
break;
}
}
}
if (!right && controller.joyconType == JOYJoyConTypeRight) {
for (JOYButton *button in controller.buttons) {
if (button.usage == JOYButtonUsageR1 && button.isPressed) {
right = controller;
break;
}
}
}
if (left && right) {
[self pairJoyCon:left withJoyCon:right];
return;
}
}
}
- (void)setAutoPairCheckbox:(NSButton *)autoPairCheckbox
{
_autoPairCheckbox = autoPairCheckbox;
[_autoPairCheckbox setState:[[NSUserDefaults standardUserDefaults] boolForKey:@"GBJoyConAutoPair"]];
}
- (IBAction)toggleAutoPair:(NSButton *)sender
{
[[NSUserDefaults standardUserDefaults] setBool:sender.state forKey:@"GBJoyConAutoPair"];
[self autopair];
}
- (void)setHorizontalCheckbox:(NSButton *)horizontalCheckbox
{
_horizontalCheckbox = horizontalCheckbox;
[_horizontalCheckbox setState:[[NSUserDefaults standardUserDefaults] boolForKey:@"GBJoyConsDefaultsToHorizontal"]];
}
- (IBAction)toggleHorizontalDefault:(NSButton *)sender
{
[[NSUserDefaults standardUserDefaults] setBool:sender.state forKey:@"GBJoyConsDefaultsToHorizontal"];
for (JOYController *controller in self.joycons) {
[self updateGripForController:controller];
}
if (_arrangementMode) {
[self.tableView reloadData];
}
}
- (void)setArrangementMode:(bool)arrangementMode
{
_arrangementMode = arrangementMode;
if (arrangementMode) {
[self.tableView reloadData];
}
}
@end