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.