git.y1.nz

SameBoy

Accurate GB/GBC emulator
download: https://git.y1.nz/archives/sameboy.tar.gz
README | Files | Log | Refs | LICENSE

Cocoa/GBPaletteEditorController.m

      1 #import "GBPaletteEditorController.h"
      2 #import "GBHueSliderCell.h"
      3 #import "GBApp.h"
      4 #import <Core/gb.h>
      5 
      6 #define MAGIC 'SBPL'
      7 
      8 typedef struct __attribute__ ((packed)) {
      9     uint32_t magic;
     10     bool manual:1;
     11     bool disabled_lcd_color:1;
     12     unsigned padding:6;
     13     struct GB_color_s colors[5];
     14     int32_t brightness_bias;
     15     uint32_t hue_bias;
     16     uint32_t hue_bias_strength;
     17 } theme_t;
     18 
     19 static double blend(double from, double to, double position)
     20 {
     21     return from * (1 - position) + to * position;
     22 }
     23 
     24 @implementation NSColor (GBColor)
     25 
     26 - (struct GB_color_s)gbColor
     27 {
     28     NSColor *sRGB = [self colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]];
     29     return (struct GB_color_s){round(sRGB.redComponent * 255), round(sRGB.greenComponent * 255), round(sRGB.blueComponent * 255)};
     30 }
     31 
     32 - (uint32_t)intValue
     33 {
     34     struct GB_color_s color = self.gbColor;
     35     return (color.r << 0) | (color.g << 8) | (color.b << 16) | 0xFF000000;
     36 }
     37 
     38 @end
     39 
     40 @implementation GBPaletteEditorController
     41 
     42 - (NSArray<NSColorWell *> *)colorWells
     43 {
     44     return @[_colorWell0, _colorWell1, _colorWell2, _colorWell3, _colorWell4];
     45 }
     46 
     47 - (void)updateEnabledControls
     48 {
     49     if (self.manualModeCheckbox.state) {
     50         _brightnessSlider.enabled = false;
     51         _hueSlider.enabled = false;
     52         _hueStrengthSlider.enabled = false;
     53         _colorWell1.enabled = true;
     54         _colorWell2.enabled = true;
     55         _colorWell3.enabled = true;
     56         if (!(_colorWell4.enabled = self.disableLCDColorCheckbox.state)) {
     57             _colorWell4.color = _colorWell3.color;
     58         }
     59     }
     60     else {
     61         _colorWell1.enabled = false;
     62         _colorWell2.enabled = false;
     63         _colorWell3.enabled = false;
     64         _colorWell4.enabled = true;
     65         _brightnessSlider.enabled = true;
     66         _hueSlider.enabled = true;
     67         _hueStrengthSlider.enabled = true;
     68         [self updateAutoColors];
     69     }
     70 }
     71 
     72 - (NSColor *)autoColorAtPositon:(double)position
     73 {
     74     NSColor *first = [_colorWell0.color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]];
     75     NSColor *second = [_colorWell4.color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]];
     76     double brightness = 1 / pow(4, (_brightnessSlider.doubleValue - 128) / 128.0);
     77     position = pow(position, brightness);
     78     NSColor *hue = _hueSlider.colorValue;
     79     double bias = _hueStrengthSlider.doubleValue / 256.0;
     80     double red = 1 / pow(4, (hue.redComponent * 2 - 1) * bias);
     81     double green = 1 / pow(4, (hue.greenComponent * 2 - 1) * bias);
     82     double blue = 1 / pow(4, (hue.blueComponent * 2 - 1) * bias);
     83     NSColor *ret = [NSColor colorWithRed:blend(first.redComponent, second.redComponent, pow(position, red))
     84                                    green:blend(first.greenComponent, second.greenComponent, pow(position, green))
     85                                     blue:blend(first.blueComponent, second.blueComponent, pow(position, blue))
     86                                    alpha:1.0];
     87     return ret;
     88 }
     89 
     90 - (IBAction)updateAutoColors:(id)sender
     91 {
     92     if (!self.manualModeCheckbox.state) {
     93         [self updateAutoColors];
     94     }
     95     else {
     96         [self savePalette:sender];
     97     }
     98 }
     99 
    100 - (void)updateAutoColors
    101 {
    102     if (_disableLCDColorCheckbox.state) {
    103         _colorWell1.color = [self autoColorAtPositon:8 / 25.0];
    104         _colorWell2.color = [self autoColorAtPositon:16 / 25.0];
    105         _colorWell3.color = [self autoColorAtPositon:24 / 25.0];
    106     }
    107     else {
    108         _colorWell1.color = [self autoColorAtPositon:1 / 3.0];
    109         _colorWell2.color = [self autoColorAtPositon:2 / 3.0];
    110         _colorWell3.color = _colorWell4.color;
    111     }
    112     [self savePalette:nil];
    113 }
    114 
    115 - (IBAction)disabledLCDColorCheckboxChanged:(id)sender
    116 {
    117     [self updateEnabledControls];
    118 }
    119 
    120 - (IBAction)manualModeChanged:(id)sender
    121 {
    122     [self updateEnabledControls];
    123 }
    124 
    125 - (IBAction)updateColor4:(id)sender
    126 {
    127     if (!self.disableLCDColorCheckbox.state) {
    128         self.colorWell4.color = self.colorWell3.color;
    129     }
    130     [self savePalette:self];
    131 }
    132 
    133 - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
    134 {
    135     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    136     NSDictionary *themes = [defaults dictionaryForKey:@"GBThemes"];
    137     if (themes.count == 0) {
    138         [defaults setObject:@"Untitled Palette" forKey:@"GBCurrentTheme"];
    139         [self savePalette:nil];
    140         return 1;
    141     }
    142     return themes.count;
    143 }
    144 
    145 -(void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
    146 {
    147     NSString *oldName = [self tableView:tableView objectValueForTableColumn:tableColumn row:row];
    148     if ([oldName isEqualToString:object]) {
    149         return;
    150     }
    151     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    152     NSMutableDictionary *themes = [[defaults dictionaryForKey:@"GBThemes"] ?: @{} mutableCopy];
    153     NSString *newName = object;
    154     unsigned i = 2;
    155     if (!newName.length) {
    156         newName = @"Untitled Palette";
    157     }
    158     while (themes[newName]) {
    159         newName = [NSString stringWithFormat:@"%@ %d", object, i];
    160     }
    161     themes[newName] = themes[oldName];
    162     [themes removeObjectForKey:oldName];
    163     if ([oldName isEqualToString:[defaults stringForKey:@"GBCurrentTheme"]]) {
    164         [defaults setObject:newName forKey:@"GBCurrentTheme"];
    165     }
    166     [defaults setObject:themes forKey:@"GBThemes"];
    167     [tableView reloadData];
    168     [self awakeFromNib];
    169 }
    170 
    171 - (IBAction)deleteTheme:(id)sender
    172 {
    173     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    174     NSString *name = [defaults stringForKey:@"GBCurrentTheme"];
    175     NSMutableDictionary *themes = [[defaults dictionaryForKey:@"GBThemes"] ?: @{} mutableCopy];
    176     [themes removeObjectForKey:name];
    177     [defaults setObject:themes forKey:@"GBThemes"];
    178     [_themesList reloadData];
    179     [self awakeFromNib];
    180 }
    181 
    182 - (void)tableViewSelectionDidChange:(NSNotification *)notification
    183 {
    184     NSString *name = [self tableView:nil objectValueForTableColumn:nil row:_themesList.selectedRow];
    185     [[NSUserDefaults standardUserDefaults] setObject:name forKey:@"GBCurrentTheme"];
    186     [self loadPalette];
    187     [[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorPaletteChanged" object:nil];
    188 }
    189 
    190 - (void)tableViewSelectionIsChanging:(NSNotification *)notification
    191 {
    192     [self tableViewSelectionDidChange:notification];
    193 }
    194 
    195 - (void)awakeFromNib
    196 {
    197     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    198     NSDictionary *themes = [defaults dictionaryForKey:@"GBThemes"];
    199     NSString *theme = [defaults stringForKey:@"GBCurrentTheme"];
    200     if (theme && themes[theme]) {
    201         unsigned index = [[themes.allKeys sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)] indexOfObject:theme];
    202         [_themesList selectRowIndexes:[NSIndexSet indexSetWithIndex:index] byExtendingSelection:false];
    203         [_themesList scrollRowToVisible:index];
    204     }
    205     else {
    206         [_themesList selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:false];
    207         [_themesList scrollRowToVisible:0];
    208     }
    209     [self tableViewSelectionDidChange:nil];
    210     if (@available(macOS 10.10, *)) {
    211         _themesList.enclosingScrollView.contentView.automaticallyAdjustsContentInsets = false;
    212         _themesList.enclosingScrollView.contentView.contentInsets = NSEdgeInsetsMake(0, 0, 10, 0);
    213     }
    214 }
    215 
    216 - (IBAction)addTheme:(id)sender
    217 {
    218     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    219     NSDictionary *themes = [defaults dictionaryForKey:@"GBThemes"];
    220     NSString *newName = @"Untitled Palette";
    221     unsigned i = 2;
    222     while (themes[newName]) {
    223         newName = [NSString stringWithFormat:@"Untitled Palette %d", i++];
    224     }
    225     [defaults setObject:newName forKey:@"GBCurrentTheme"];
    226     [self savePalette:sender];
    227     [_themesList reloadData];
    228     [self awakeFromNib];
    229 }
    230 
    231 - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
    232 {
    233     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    234     NSDictionary *themes = [defaults dictionaryForKey:@"GBThemes"];
    235     return [themes.allKeys sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)][row];
    236 }
    237 
    238 - (void)loadPalette
    239 {
    240     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    241     NSDictionary *theme = [defaults dictionaryForKey:@"GBThemes"][[defaults stringForKey:@"GBCurrentTheme"]];
    242     NSArray *colors = theme[@"Colors"];
    243     if (colors.count == 5) {
    244         unsigned i = 0;
    245         for (NSNumber *color in colors) {
    246             uint32_t c = [color unsignedIntValue];
    247             self.colorWells[i++].color = [NSColor colorWithRed:(c & 0xFF) / 255.0
    248                                                        green:((c >> 8) & 0xFF) / 255.0
    249                                                         blue:((c >> 16) & 0xFF) / 255.0
    250                                                        alpha:1.0];
    251         }
    252     }
    253     _disableLCDColorCheckbox.state = [theme[@"DisabledLCDColor"] boolValue];
    254     _manualModeCheckbox.state = [theme[@"Manual"] boolValue];
    255     _brightnessSlider.doubleValue = [theme[@"BrightnessBias"] doubleValue] * 128 + 128;
    256     _hueSlider.doubleValue = [theme[@"HueBias"] doubleValue] * 360;
    257     _hueStrengthSlider.doubleValue = [theme[@"HueBiasStrength"] doubleValue] * 256;
    258     [self updateEnabledControls];
    259 }
    260 
    261 - (IBAction)savePalette:(id)sender
    262 {
    263     NSDictionary *theme = @{
    264         @"Colors":
    265             @[@(_colorWell0.color.intValue),
    266               @(_colorWell1.color.intValue),
    267               @(_colorWell2.color.intValue),
    268               @(_colorWell3.color.intValue),
    269               @(_colorWell4.color.intValue)],
    270         @"DisabledLCDColor": _disableLCDColorCheckbox.state? @YES : @NO,
    271         @"Manual": _manualModeCheckbox.state? @YES : @NO,
    272         @"BrightnessBias": @((_brightnessSlider.doubleValue - 128) / 128.0),
    273         @"HueBias": @(_hueSlider.doubleValue / 360.0),
    274         @"HueBiasStrength": @(_hueStrengthSlider.doubleValue / 256.0)
    275     };
    276     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    277     NSMutableDictionary *themes = [[defaults dictionaryForKey:@"GBThemes"] ?: @{} mutableCopy];
    278     themes[[defaults stringForKey:@"GBCurrentTheme"]] = theme;
    279     [defaults setObject:themes forKey:@"GBThemes"];
    280     [[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorPaletteChanged" object:nil];
    281 }
    282 
    283 + (const GB_palette_t *)userPalette
    284 {
    285     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    286     switch ([defaults integerForKey:@"GBColorPalette"]) {
    287         case 1: return &GB_PALETTE_DMG;
    288         case 2: return &GB_PALETTE_MGB;
    289         case 3: return &GB_PALETTE_GBL;
    290         default: return &GB_PALETTE_GREY;
    291         case -1: {
    292             static GB_palette_t customPalette;
    293             NSArray *colors = [defaults dictionaryForKey:@"GBThemes"][[defaults stringForKey:@"GBCurrentTheme"]][@"Colors"];
    294             if (colors.count == 5) {
    295                 unsigned i = 0;
    296                 for (NSNumber *color in colors) {
    297                     uint32_t c = [color unsignedIntValue];
    298                     customPalette.colors[i++] = (struct GB_color_s) {c, c >> 8, c >> 16};
    299                 }
    300             }
    301             return &customPalette;
    302         }
    303     }
    304 }
    305 
    306 - (IBAction)export:(id)sender
    307 {
    308     NSSavePanel *savePanel = [NSSavePanel savePanel];
    309     [savePanel setAllowedFileTypes:@[@"sbp"]];
    310     savePanel.nameFieldStringValue = [NSString stringWithFormat:@"%@.sbp", [[NSUserDefaults standardUserDefaults] stringForKey:@"GBCurrentTheme"]];
    311     if ([savePanel runModal] == NSModalResponseOK) {
    312         theme_t theme = {0,};
    313         theme.magic = MAGIC;
    314         theme.manual = _manualModeCheckbox.state;
    315         theme.disabled_lcd_color = _disableLCDColorCheckbox.state;
    316         unsigned i = 0;
    317         for (NSColorWell *well in self.colorWells) {
    318             theme.colors[i++] = well.color.gbColor;
    319         }
    320         theme.brightness_bias = (_brightnessSlider.doubleValue - 128) * (0x40000000 / 128);
    321         theme.hue_bias = round(_hueSlider.doubleValue * (0x80000000 / 360.0));
    322         theme.hue_bias_strength = (_hueStrengthSlider.doubleValue) * (0x80000000 / 256);
    323         size_t size = sizeof(theme);
    324         if (theme.manual) {
    325             size = theme.disabled_lcd_color? 5 + 5 * sizeof(theme.colors[0]) : 5 + 4 * sizeof(theme.colors[0]);
    326         }
    327         [[NSData dataWithBytes:&theme length:size] writeToURL:savePanel.URL atomically:false];
    328     }
    329 }
    330 
    331 - (IBAction)import:(id)sender
    332 {
    333     NSOpenPanel *openPanel = [NSOpenPanel openPanel];
    334     [openPanel setAllowedFileTypes:@[@"sbp"]];
    335     if ([openPanel runModal] == NSModalResponseOK) {
    336         NSData *data = [NSData dataWithContentsOfURL:openPanel.URL];
    337         theme_t theme = {0,};
    338         memcpy(&theme, data.bytes, MIN(sizeof(theme), data.length));
    339         if (theme.magic != MAGIC) {
    340             NSBeep();
    341             return;
    342         }
    343         _manualModeCheckbox.state = theme.manual;
    344         _disableLCDColorCheckbox.state = theme.disabled_lcd_color;
    345         unsigned i = 0;
    346         for (NSColorWell *well in self.colorWells) {
    347             well.color = [NSColor colorWithRed:theme.colors[i].r / 255.0
    348                                          green:theme.colors[i].g / 255.0
    349                                           blue:theme.colors[i].b / 255.0
    350                                          alpha:1.0];
    351             i++;
    352         }
    353         if (!theme.disabled_lcd_color) {
    354             _colorWell4.color = _colorWell3.color;
    355         }
    356         _brightnessSlider.doubleValue = theme.brightness_bias / (0x40000000 / 128.0) + 128;
    357         _hueSlider.doubleValue = theme.hue_bias / (0x80000000 / 360.0);
    358         _hueStrengthSlider.doubleValue = theme.hue_bias_strength / (0x80000000 / 256.0);
    359         
    360         NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    361         NSDictionary *themes = [defaults dictionaryForKey:@"GBThemes"];
    362         NSString *baseName = openPanel.URL.lastPathComponent.stringByDeletingPathExtension;
    363         NSString *newName = baseName;
    364         i = 2;
    365         while (themes[newName]) {
    366             newName = [NSString stringWithFormat:@"%@ %d", baseName, i++];
    367         }
    368         [defaults setObject:newName forKey:@"GBCurrentTheme"];
    369         [self savePalette:sender];
    370         [self.themesList reloadData];
    371         [self awakeFromNib];
    372     }
    373 }
    374 
    375 - (IBAction)segmentAction:(NSSegmentedControl *)sender
    376 {
    377     switch (sender.selectedSegment) {
    378         case 0: [self addTheme:sender]; return;
    379         case 1: [self deleteTheme:sender]; return;
    380         case 3: {
    381             NSSize buttonSize = _segmentControl.bounds.size;
    382             [_segmentMenu popUpMenuPositioningItem:_segmentMenu.itemArray.lastObject
    383                                         atLocation:NSMakePoint(buttonSize.width,
    384                                                                0)
    385                                             inView:sender];
    386             return;
    387         }
    388     }
    389 }
    390 
    391 - (IBAction)restoreDefaultPalettes:(id)sender
    392 {
    393     [(GBApp *)NSApp updateThemesDefault:true];
    394     [self awakeFromNib];
    395 }
    396 
    397 - (IBAction)done:(NSButton *)sender
    398 {
    399     [sender.window.sheetParent endSheet:sender.window];
    400 }
    401 
    402 + (instancetype)alloc
    403 {
    404     static id singleton = nil;
    405     if (singleton) return singleton;
    406     return (singleton = [super allocWithZone:nil]);
    407 }
    408 
    409 + (instancetype)allocWithZone:(struct _NSZone *)zone
    410 {
    411     return [self alloc];
    412 }
    413 
    414 @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.