git.y1.nz

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.