Settings screen
|
@ -185,7 +185,7 @@ static void positionView(UIImageView *view, CGPoint position)
|
|||
}
|
||||
|
||||
GB_set_key_mask(_gbView.gb, mask);
|
||||
if (mask & ~_lastMask) {
|
||||
if ((mask & ~_lastMask) && ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBButtonHaptics"])) {
|
||||
[[GBHapticManager sharedManager] doTapHaptic];
|
||||
}
|
||||
_lastMask = mask;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#import "GBLoadROMTableViewController.h"
|
||||
#import "GBTableViewCell.h"
|
||||
#import "GBROMManager.h"
|
||||
|
||||
@interface GBLoadROMTableViewController ()
|
||||
|
@ -27,7 +26,7 @@
|
|||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
GBTableViewCell *cell = [[GBTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
|
||||
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
|
||||
NSString *rom = [GBROMManager sharedManager].allROMs[[indexPath indexAtPosition:1]];
|
||||
cell.textLabel.text = rom;
|
||||
cell.accessoryType = [rom isEqualToString:[GBROMManager sharedManager].currentROM]? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
{@"Library", @"bookmark", SelectorString(openLibrary)},
|
||||
{@"Model", @"ModelTemplate", SelectorString(changeModel)},
|
||||
{@"States", @"square.stack", nil},
|
||||
{@"Settings", @"gear", nil},
|
||||
{@"Settings", @"gear", SelectorString(openSettings)},
|
||||
{@"About", @"info.circle", SelectorString(showAbout)},
|
||||
};
|
||||
|
||||
|
|
|
@ -58,6 +58,7 @@
|
|||
|
||||
- (NSString *)romFileForROM:(NSString *)rom
|
||||
{
|
||||
if ([rom isEqualToString:@"Inbox"]) return nil;
|
||||
if (rom == _currentROM) {
|
||||
return self.romFile;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
#import <UIKit/UIKit.h>
|
||||
#include <Core/gb.h>
|
||||
|
||||
@interface GBSettingsViewController : UITableViewController
|
||||
+ (UIViewController *)settingsViewControllerWithLeftButton:(UIBarButtonItem *)button;
|
||||
+ (const GB_palette_t *)paletteForTheme:(NSString *)theme;
|
||||
@end
|
|
@ -0,0 +1,595 @@
|
|||
#import "GBSettingsViewController.h"
|
||||
#import "GBTemperatureSlider.h"
|
||||
#import "GBViewBase.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
static NSString const *typeSubmenu = @"submenu";
|
||||
static NSString const *typeOptionSubmenu = @"optionSubmenu";
|
||||
static NSString const *typeRadio = @"radio";
|
||||
static NSString const *typeCheck = @"check";
|
||||
static NSString const *typeDisabled = @"disabled";
|
||||
static NSString const *typeSeparator = @"separator";
|
||||
static NSString const *typeSlider = @"slider";
|
||||
static NSString const *typeLightTemp = @"typeLightTemp";
|
||||
|
||||
@implementation GBSettingsViewController
|
||||
{
|
||||
NSArray<NSDictionary *> *_structure;
|
||||
UINavigationController *_detailsNavigation;
|
||||
}
|
||||
|
||||
+ (const GB_palette_t *)paletteForTheme:(NSString *)theme
|
||||
{
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
if ([theme isEqualToString:@"Greyscale"]) {
|
||||
return &GB_PALETTE_GREY;
|
||||
}
|
||||
if ([theme isEqualToString:@"Lime (Game Boy)"]) {
|
||||
return &GB_PALETTE_DMG;
|
||||
}
|
||||
if ([theme isEqualToString:@"Olive (Pocket)"]) {
|
||||
return &GB_PALETTE_MGB;
|
||||
}
|
||||
if ([theme isEqualToString:@"Teal (Light)"]) {
|
||||
return &GB_PALETTE_GBL;
|
||||
}
|
||||
static GB_palette_t customPalette;
|
||||
NSArray *colors = [defaults dictionaryForKey:@"GBThemes"][theme][@"Colors"];
|
||||
if (colors.count != 5) return &GB_PALETTE_DMG;
|
||||
unsigned i = 0;
|
||||
for (NSNumber *color in colors) {
|
||||
uint32_t c = [color unsignedIntValue];
|
||||
customPalette.colors[i++] = (struct GB_color_s) {c, c >> 8, c >> 16};
|
||||
}
|
||||
return &customPalette;
|
||||
}
|
||||
|
||||
+ (UIColor *) colorFromGBColor:(const struct GB_color_s *)color
|
||||
{
|
||||
return [UIColor colorWithRed:color->r / 255.0
|
||||
green:color->g / 255.0
|
||||
blue:color->b / 255.0
|
||||
alpha:1.0];
|
||||
}
|
||||
|
||||
+ (UIImage *)previewImageForTheme:(NSString *)theme
|
||||
{
|
||||
const GB_palette_t *palette = [self paletteForTheme:theme];
|
||||
UIGraphicsBeginImageContextWithOptions((CGSize){29, 29}, false, [UIScreen mainScreen].scale);
|
||||
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 29, 29) cornerRadius:7];
|
||||
[[self colorFromGBColor:&palette->colors[4]] set];
|
||||
[path fill];
|
||||
|
||||
path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(4, 4, 9, 9) cornerRadius:2];
|
||||
[[self colorFromGBColor:&palette->colors[0]] set];
|
||||
[path fill];
|
||||
|
||||
path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(16, 4, 9, 9) cornerRadius:2];
|
||||
[[self colorFromGBColor:&palette->colors[1]] set];
|
||||
[path fill];
|
||||
|
||||
path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(4, 16, 9, 9) cornerRadius:2];
|
||||
[[self colorFromGBColor:&palette->colors[2]] set];
|
||||
[path fill];
|
||||
|
||||
path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(16, 16, 9, 9) cornerRadius:2];
|
||||
[[self colorFromGBColor:&palette->colors[3]] set];
|
||||
[path fill];
|
||||
|
||||
UIImage *ret = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
+ (NSArray<NSDictionary *> *)paletteMenu
|
||||
{
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
NSArray *themes = [@[
|
||||
@"Greyscale",
|
||||
@"Lime (Game Boy)",
|
||||
@"Olive (Pocket)",
|
||||
@"Teal (Light)",
|
||||
] arrayByAddingObjectsFromArray:[[defaults dictionaryForKey:@"GBThemes"] allKeys]];
|
||||
NSMutableArray<NSDictionary *> *themeItems = [NSMutableArray arrayWithCapacity:themes.count];
|
||||
for (NSString *theme in themes) {
|
||||
[themeItems addObject: @{@"type": typeRadio, @"pref": @"GBCurrentTheme",
|
||||
@"title": theme, @"value": theme,
|
||||
@"image": [self previewImageForTheme:theme]}];
|
||||
}
|
||||
return @[
|
||||
@{
|
||||
@"items": [themeItems subarrayWithRange:(NSRange){0, 4}]
|
||||
},
|
||||
@{
|
||||
@"items": [themeItems subarrayWithRange:(NSRange){4, themeItems.count - 4}]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
+ (NSArray<NSDictionary *> *)rootStructure
|
||||
{
|
||||
#define QUICK_SUBMENU(title, ...) @{@"type": typeOptionSubmenu, @"title": title, @"submenu": @[@{@"items": __VA_ARGS__}]}
|
||||
NSArray<NSDictionary *> *emulationMenu = @[
|
||||
@{
|
||||
@"header": @"Rewind duration",
|
||||
@"items": @[
|
||||
@{@"type": typeRadio, @"pref": @"GBRewindLength", @"title": @"Disabled", @"value": @0,},
|
||||
@{@"type": typeRadio, @"pref": @"GBRewindLength", @"title": @"10 Seconds", @"value": @10,},
|
||||
@{@"type": typeRadio, @"pref": @"GBRewindLength", @"title": @"30 Seconds", @"value": @30,},
|
||||
@{@"type": typeRadio, @"pref": @"GBRewindLength", @"title": @"1 Minute", @"value": @60,},
|
||||
@{@"type": typeRadio, @"pref": @"GBRewindLength", @"title": @"2 Minutes", @"value": @120,},
|
||||
@{@"type": typeRadio, @"pref": @"GBRewindLength", @"title": @"5 Minutes", @"value": @300,},
|
||||
@{@"type": typeRadio, @"pref": @"GBRewindLength", @"title": @"10 Minutes", @"value": @600,},
|
||||
]
|
||||
},
|
||||
@{
|
||||
@"header": @"Real Time Clock emulation",
|
||||
@"items": @[
|
||||
@{@"type": typeRadio, @"pref": @"GBRTCMode", @"title": @"Accurate", @"value": @(GB_RTC_MODE_ACCURATE),},
|
||||
@{@"type": typeRadio, @"pref": @"GBRTCMode", @"title": @"Sync to system clock", @"value": @(GB_RTC_MODE_SYNC_TO_HOST),},
|
||||
]
|
||||
},
|
||||
@{
|
||||
@"header": @"Emulated revisions",
|
||||
@"items": @[
|
||||
QUICK_SUBMENU(@"Game Boy", @[
|
||||
@{@"type": typeDisabled, @"title": @"DMG-CPU 0"},
|
||||
@{@"type": typeDisabled, @"title": @"DMG-CPU A"},
|
||||
@{@"type": typeRadio, @"pref": @"GBDMGModel", @"title": @"DMG-CPU B", @"value": @(GB_MODEL_DMG_B),},
|
||||
@{@"type": typeDisabled, @"title": @"DMG-CPU C"},
|
||||
]),
|
||||
QUICK_SUBMENU(@"Super Game Boy", @[
|
||||
@{@"type": typeRadio, @"pref": @"GBSGBModel", @"title": @"Super Game Boy (NTSC)", @"value": @(GB_MODEL_SGB_NTSC),},
|
||||
@{@"type": typeRadio, @"pref": @"GBSGBModel", @"title": @"Super Game Boy (PAL)", @"value": @(GB_MODEL_SGB_PAL),},
|
||||
@{@"type": typeRadio, @"pref": @"GBSGBModel", @"title": @"Super Game Boy 2", @"value": @(GB_MODEL_SGB2),},
|
||||
]),
|
||||
QUICK_SUBMENU(@"Game Boy Color", @[
|
||||
@{@"type": typeRadio, @"pref": @"GBCGBModel", @"title": @"CPU CGB 0 (Experimental)", @"value": @(GB_MODEL_CGB_0),},
|
||||
@{@"type": typeRadio, @"pref": @"GBCGBModel", @"title": @"CPU CGB A (Experimental)", @"value": @(GB_MODEL_CGB_A),},
|
||||
@{@"type": typeRadio, @"pref": @"GBCGBModel", @"title": @"CPU CGB B (Experimental)", @"value": @(GB_MODEL_CGB_B),},
|
||||
@{@"type": typeRadio, @"pref": @"GBCGBModel", @"title": @"CPU CGB C (Experimental)", @"value": @(GB_MODEL_CGB_C),},
|
||||
@{@"type": typeRadio, @"pref": @"GBCGBModel", @"title": @"CPU CGB D", @"value": @(GB_MODEL_CGB_D),},
|
||||
@{@"type": typeRadio, @"pref": @"GBCGBModel", @"title": @"CPU CGB E", @"value": @(GB_MODEL_CGB_E),},
|
||||
]),
|
||||
QUICK_SUBMENU(@"Game Boy Advance", @[
|
||||
@{@"type": typeDisabled, @"title": @"CPU AGB 0 (Early GBA)",},
|
||||
@{@"type": typeRadio, @"pref": @"GBAGBModel", @"title": @"CPU AGB A (GBA)", @"value": @(GB_MODEL_AGB_A),},
|
||||
@{@"type": typeRadio, @"pref": @"GBAGBModel", @"title": @"CPU AGB A (Game Boy Player)", @"value": @(GB_MODEL_GBP_A),},
|
||||
@{@"type": typeDisabled, @"title": @"CPU AGB B (GBA SP)",},
|
||||
@{@"type": typeDisabled, @"title": @"CPU AGB E (Late GBA SP)",},
|
||||
@{@"type": typeDisabled, @"title": @"CPU AGB E (Late Game Boy Player)",},
|
||||
]),
|
||||
],
|
||||
@"footer": @"Changing the emulated revision on active ROMs will take effect after selecting Reset from the menu or changing the emulated model.",
|
||||
},
|
||||
];
|
||||
|
||||
NSArray<NSDictionary *> *videoMenu = @[
|
||||
@{
|
||||
@"items": @[
|
||||
@{
|
||||
@"title": @"Graphics filter",
|
||||
@"type": typeOptionSubmenu,
|
||||
@"submenu": @[
|
||||
@{
|
||||
@"header": @"Standard filters",
|
||||
@"items": @[
|
||||
@{@"type": typeRadio, @"pref": @"GBFilter", @"title": @"Nearest Neighbor (Pixelated)", @"value": @"NearestNeighbor",},
|
||||
@{@"type": typeRadio, @"pref": @"GBFilter", @"title": @"Bilinear (Blurry)", @"value": @"Bilinear",},
|
||||
@{@"type": typeRadio, @"pref": @"GBFilter", @"title": @"Smooth Bilinear (Less blurry)", @"value": @"SmoothBilinear",},
|
||||
]
|
||||
},
|
||||
@{
|
||||
@"header": @"Screen filters",
|
||||
@"items": @[
|
||||
@{@"type": typeRadio, @"pref": @"GBFilter", @"title": @"Monochrome LCD Display", @"value": @"MonoLCD",},
|
||||
@{@"type": typeRadio, @"pref": @"GBFilter", @"title": @"LCD Display", @"value": @"LCD",},
|
||||
@{@"type": typeRadio, @"pref": @"GBFilter", @"title": @"CRT Display", @"value": @"CRT",},
|
||||
]
|
||||
},
|
||||
@{
|
||||
@"header": @"Upscaling filters",
|
||||
@"items": @[
|
||||
@{@"type": typeRadio, @"pref": @"GBFilter", @"title": @"Scale2x", @"value": @"Scale2x"},
|
||||
@{@"type": typeRadio, @"pref": @"GBFilter", @"title": @"Scale4x", @"value": @"Scale4x"},
|
||||
@{@"type": typeRadio, @"pref": @"GBFilter", @"title": @"Anti-aliased Scale2x", @"value": @"AAScale2x"},
|
||||
@{@"type": typeRadio, @"pref": @"GBFilter", @"title": @"Anti-aliased Scale4x", @"value": @"AAScale4x"},
|
||||
@{@"type": typeRadio, @"pref": @"GBFilter", @"title": @"HQ2x", @"value": @"HQ2x"},
|
||||
@{@"type": typeRadio, @"pref": @"GBFilter", @"title": @"OmniScale", @"value": @"OmniScale"},
|
||||
@{@"type": typeRadio, @"pref": @"GBFilter", @"title": @"OmniScale Legacy", @"value": @"OmniScaleLegacy"},
|
||||
@{@"type": typeRadio, @"pref": @"GBFilter", @"title": @"AA OmniScale Legacy", @"value": @"AAOmniScaleLegacy"},
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
@{
|
||||
@"header": @"Color Correction",
|
||||
@"items": @[
|
||||
@{@"type": typeRadio, @"pref": @"GBColorCorrection", @"title": @"Disabled", @"value": @(GB_COLOR_CORRECTION_DISABLED),},
|
||||
@{@"type": typeRadio, @"pref": @"GBColorCorrection", @"title": @"Correct color curves", @"value": @(GB_COLOR_CORRECTION_CORRECT_CURVES),},
|
||||
@{@"type": typeSeparator},
|
||||
@{@"type": typeRadio, @"pref": @"GBColorCorrection", @"title": @"Modern – Balanced", @"value": @(GB_COLOR_CORRECTION_MODERN_BALANCED),},
|
||||
@{@"type": typeRadio, @"pref": @"GBColorCorrection", @"title": @"Modern – Accurate", @"value": @(GB_COLOR_CORRECTION_MODERN_ACCURATE),},
|
||||
@{@"type": typeRadio, @"pref": @"GBColorCorrection", @"title": @"Modern – Boost contrast", @"value": @(GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST),},
|
||||
@{@"type": typeSeparator},
|
||||
@{@"type": typeRadio, @"pref": @"GBColorCorrection", @"title": @"Reduce contrast", @"value": @(GB_COLOR_CORRECTION_REDUCE_CONTRAST),},
|
||||
@{@"type": typeRadio, @"pref": @"GBColorCorrection", @"title": @"Harsh reality (low contrast)", @"value": @(GB_COLOR_CORRECTION_LOW_CONTRAST),},
|
||||
],
|
||||
@"footer": ^NSString *(){
|
||||
return (NSString * const[]){
|
||||
[GB_COLOR_CORRECTION_DISABLED] = @"Colors are directly interpreted as sRGB, resulting in unbalanced colors and inaccurate hues.",
|
||||
[GB_COLOR_CORRECTION_CORRECT_CURVES] = @"Colors have their brightness corrected, but hues remain unbalanced.",
|
||||
[GB_COLOR_CORRECTION_MODERN_BALANCED] = @"Emulates a modern display. Blue contrast is moderately enhanced at the cost of slight hue inaccuracy.",
|
||||
[GB_COLOR_CORRECTION_MODERN_BOOST_CONTRAST] = @"Like Modern – Balanced, but further boosts the contrast of greens and magentas that is lacking on the original hardware.",
|
||||
[GB_COLOR_CORRECTION_REDUCE_CONTRAST] = @"Slightly reduce the contrast to better represent the tint and contrast of the original display.",
|
||||
[GB_COLOR_CORRECTION_LOW_CONTRAST] = @"Harshly reduce the contrast to accurately represent the tint low constrast of the original display.",
|
||||
[GB_COLOR_CORRECTION_MODERN_ACCURATE] = @"Emulates a modern display. Colors have their hues and brightness corrected.",
|
||||
}[MIN(GB_COLOR_CORRECTION_MODERN_ACCURATE, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorCorrection"])];
|
||||
},
|
||||
},
|
||||
@{
|
||||
@"header": @"Ambient light temperature",
|
||||
@"items": @[
|
||||
@{@"type": typeLightTemp, @"pref": @"GBLightTemperature", @"min": @-1, @"max": @1}
|
||||
],
|
||||
},
|
||||
@{
|
||||
@"header": @"Frame blending",
|
||||
@"items": @[
|
||||
@{@"type": typeRadio, @"pref": @"GBFrameBlendingMode", @"title": @"Disabled", @"value": @(GB_FRAME_BLENDING_MODE_DISABLED),},
|
||||
@{@"type": typeRadio, @"pref": @"GBFrameBlendingMode", @"title": @"Simple", @"value": @(GB_FRAME_BLENDING_MODE_SIMPLE),},
|
||||
@{@"type": typeRadio, @"pref": @"GBFrameBlendingMode", @"title": @"Accurate", @"value": @(GB_FRAME_BLENDING_MODE_ACCURATE),},
|
||||
]
|
||||
},
|
||||
@{
|
||||
@"items": @[@{
|
||||
@"title": @"Monochrome palette",
|
||||
@"type": typeOptionSubmenu,
|
||||
@"submenu": [self paletteMenu]
|
||||
}],
|
||||
@"footer": @"This palette will be used when emulating a monochrome model such as the original Game Boy."
|
||||
}
|
||||
];
|
||||
|
||||
NSArray<NSDictionary *> *audioMenu = @[
|
||||
@{
|
||||
@"header": @"High-pass filter",
|
||||
@"items": @[
|
||||
@{@"type": typeRadio, @"pref": @"GBHighpassFilter", @"title": @"Disabled (Keep DC offset)", @"value": @(GB_HIGHPASS_OFF),},
|
||||
@{@"type": typeRadio, @"pref": @"GBHighpassFilter", @"title": @"Accurate (Emulate hardware)", @"value": @(GB_HIGHPASS_ACCURATE),},
|
||||
@{@"type": typeRadio, @"pref": @"GBHighpassFilter", @"title": @"Preserve waveform", @"value": @(GB_HIGHPASS_REMOVE_DC_OFFSET),},
|
||||
],
|
||||
@"footer": ^NSString *(){
|
||||
return (NSString * const[]){
|
||||
[GB_HIGHPASS_OFF] = @"No high-pass filter will be applied. DC offset will be kept, pausing and resuming will trigger audio pops.",
|
||||
[GB_HIGHPASS_ACCURATE] = @"An accurate high-pass filter will be applied, removing the DC offset while somewhat attenuating the bass.",
|
||||
[GB_HIGHPASS_REMOVE_DC_OFFSET] = @"A high-pass filter will be applied to the DC offset itself, removing the DC offset while preserving the waveform.",
|
||||
}[MIN(GB_HIGHPASS_REMOVE_DC_OFFSET, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBHighpassFilter"])];
|
||||
},
|
||||
},
|
||||
@{
|
||||
@"header": @"Interference volume",
|
||||
@"items": @[
|
||||
@{@"type": typeSlider, @"pref": @"GBInterferenceVolume", @"min": @0, @"max": @1, @"minImage": @"speaker.fill", @"maxImage": @"speaker.3.fill"}
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
NSArray<NSDictionary *> *controlsMenu = @[
|
||||
@{
|
||||
@"header": @"D-pad style",
|
||||
@"items": @[
|
||||
// TODO: Convert to enum when implemented
|
||||
@{@"type": typeRadio, @"pref": @"GBDpadMode", @"title": @"Standard", @"value": @(0),},
|
||||
@{@"type": typeRadio, @"pref": @"GBDpadMode", @"title": @"Swipe", @"value": @(1),},
|
||||
],
|
||||
@"footer": ^NSString *(){
|
||||
return (NSString * const[]){
|
||||
[0] = @"Directional input is determined by the touch position.",
|
||||
[1] = @"Directional input is determined by the swipe direction.",
|
||||
}[MIN(1, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBDpadMode"])];
|
||||
},
|
||||
},
|
||||
@{
|
||||
@"header": @"Fast-forward and rewind behavior",
|
||||
@"items": @[
|
||||
@{@"type": typeCheck, @"pref": @"GBSwipeLock", @"title": @"Lock after swiping"},
|
||||
],
|
||||
@"footer": ^NSString *(){
|
||||
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBSwipeLock"]) {
|
||||
return @"Swipe right on the Game Boy screen to fast-forward, and swipe left to rewind. Tap on the Game Boy screen to return to normal.";
|
||||
}
|
||||
return @"Swipe right on the Game Boy screen to fast-forward, and swipe left to rewind. Raise the touch to return to normal.";
|
||||
},
|
||||
},
|
||||
@{
|
||||
@"header": @"Quick save and load",
|
||||
@"items": @[
|
||||
@{@"type": typeCheck, @"pref": @"GBSwipeState", @"title": @"Swipe to save and load from slot 1"},
|
||||
],
|
||||
@"footer": ^NSString *(){
|
||||
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBSwipeState"]) {
|
||||
return @"Swipe down on the Game Boy to save the state into state slot 1. Swipe up to load the state from state slot 1";
|
||||
}
|
||||
return @" "; // This space is needed, otherwise UITableView spacing breaks
|
||||
},
|
||||
},
|
||||
@{
|
||||
@"header": @"Enable rumble",
|
||||
@"items": @[
|
||||
@{@"type": typeRadio, @"pref": @"GBRumbleMode", @"title": @"Never", @"value": @(GB_RUMBLE_DISABLED),},
|
||||
@{@"type": typeRadio, @"pref": @"GBRumbleMode", @"title": @"For rumble-enabled Game Paks", @"value": @(GB_RUMBLE_CARTRIDGE_ONLY),},
|
||||
@{@"type": typeRadio, @"pref": @"GBRumbleMode", @"title": @"Always", @"value": @(GB_RUMBLE_ALL_GAMES),},
|
||||
],
|
||||
},
|
||||
@{
|
||||
@"items": @[
|
||||
@{@"type": typeCheck, @"pref": @"GBButtonHaptics", @"title": @"Enable button haptics"},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return @[
|
||||
@{
|
||||
@"items": @[
|
||||
@{
|
||||
@"title": @"Emulation",
|
||||
@"type": typeSubmenu,
|
||||
@"submenu": emulationMenu,
|
||||
@"image": [UIImage imageNamed:@"emulationSettings"],
|
||||
},
|
||||
@{
|
||||
@"title": @"Video",
|
||||
@"type": typeSubmenu,
|
||||
@"submenu": videoMenu,
|
||||
@"image": [UIImage imageNamed:@"videoSettings"],
|
||||
},
|
||||
@{
|
||||
@"title": @"Audio",
|
||||
@"type": typeSubmenu,
|
||||
@"submenu": audioMenu,
|
||||
@"image": [UIImage imageNamed:@"audioSettings"],
|
||||
},
|
||||
@{
|
||||
@"title": @"Controls",
|
||||
@"type": typeSubmenu,
|
||||
@"submenu": controlsMenu,
|
||||
@"image": [UIImage imageNamed:@"controlsSettings"],
|
||||
},
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
+ (UIViewController *)settingsViewControllerWithLeftButton:(UIBarButtonItem *)button
|
||||
{
|
||||
GBSettingsViewController *root = [[self alloc] initWithStructure:[self rootStructure] title:@"Settings" style:UITableViewStyleGrouped];
|
||||
UINavigationController *controller = [[UINavigationController alloc] initWithRootViewController:root];
|
||||
[controller.visibleViewController.navigationItem setLeftBarButtonItem:button];
|
||||
if ([UIDevice currentDevice].userInterfaceIdiom != UIUserInterfaceIdiomPad) {
|
||||
return controller;
|
||||
}
|
||||
|
||||
UISplitViewController *split = [[UISplitViewController alloc] init];
|
||||
UIViewController *blank = [[UIViewController alloc] init];
|
||||
blank.view.backgroundColor = root.view.backgroundColor;
|
||||
root->_detailsNavigation = [[UINavigationController alloc] initWithRootViewController:blank];
|
||||
split.viewControllers = @[controller, root->_detailsNavigation];
|
||||
split.preferredDisplayMode = UISplitViewControllerDisplayModeAllVisible;
|
||||
return split;
|
||||
}
|
||||
|
||||
- (instancetype)initWithStructure:(NSArray *)structure title:(NSString *)title style:(UITableViewStyle)style
|
||||
{
|
||||
self = [super initWithStyle:style];
|
||||
if (!self) return nil;
|
||||
self.title = title;
|
||||
_structure = structure;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
||||
{
|
||||
return _structure.count;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
return [_structure[section][@"items"] count];
|
||||
}
|
||||
|
||||
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
return _structure[section][@"header"];
|
||||
}
|
||||
|
||||
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section
|
||||
{
|
||||
if ([_structure[section][@"footer"] respondsToSelector:@selector(invoke)]) {
|
||||
return ((NSString *(^)(void))_structure[section][@"footer"])();
|
||||
}
|
||||
return _structure[section][@"footer"];
|
||||
}
|
||||
|
||||
- (NSDictionary *)itemForIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return _structure[[indexPath indexAtPosition:0]][@"items"][[indexPath indexAtPosition:1]];
|
||||
}
|
||||
|
||||
- (NSDictionary *)followingItemForIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
NSArray *items = _structure[[indexPath indexAtPosition:0]][@"items"];
|
||||
if ([indexPath indexAtPosition:1] + 1 >= items.count) {
|
||||
return nil;
|
||||
}
|
||||
return items[[indexPath indexAtPosition:1] + 1];
|
||||
}
|
||||
|
||||
+ (void)fixSliderTint:(UIView *)view
|
||||
{
|
||||
if ([view isKindOfClass:[UIImageView class]]) {
|
||||
view.tintColor = [UIColor systemGrayColor];
|
||||
}
|
||||
for (UIView *subview in view.subviews) {
|
||||
[self fixSliderTint:subview];
|
||||
}
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
NSDictionary *item = [self itemForIndexPath:indexPath];
|
||||
|
||||
|
||||
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:nil];
|
||||
cell.textLabel.text = item[@"title"];
|
||||
if (item[@"type"] == typeSubmenu || item[@"type"] == typeOptionSubmenu) {
|
||||
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
|
||||
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
|
||||
if (item[@"type"] == typeOptionSubmenu) {
|
||||
for (NSDictionary *section in item[@"submenu"]) {
|
||||
for (NSDictionary *item in section[@"items"]) {
|
||||
if (item[@"value"] && [([[NSUserDefaults standardUserDefaults] objectForKey:item[@"pref"]] ?: @0) isEqual:item[@"value"]]) {
|
||||
cell.detailTextLabel.text = item[@"title"];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (item[@"type"] == typeRadio) {
|
||||
if ([([[NSUserDefaults standardUserDefaults] objectForKey:item[@"pref"]] ?: @0) isEqual:item[@"value"]]) {
|
||||
cell.accessoryType = UITableViewCellAccessoryCheckmark;
|
||||
}
|
||||
}
|
||||
else if (item[@"type"] == typeCheck) {
|
||||
UISwitch *button = [[UISwitch alloc] init];
|
||||
cell.accessoryView = button;
|
||||
if ([[NSUserDefaults standardUserDefaults] boolForKey:item[@"pref"]]) {
|
||||
button.on = true;
|
||||
}
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
id block = ^(){
|
||||
[[NSUserDefaults standardUserDefaults] setBool:button.on forKey:item[@"pref"]];
|
||||
unsigned section = [indexPath indexAtPosition:0];
|
||||
UITableViewHeaderFooterView *view = [weakSelf.tableView footerViewForSection:section];
|
||||
view.textLabel.text = [weakSelf tableView:weakSelf.tableView titleForFooterInSection:section];
|
||||
[UIView setAnimationsEnabled:false];
|
||||
[weakSelf.tableView beginUpdates];
|
||||
[view sizeToFit];
|
||||
[weakSelf.tableView endUpdates];
|
||||
[UIView setAnimationsEnabled:true];
|
||||
};
|
||||
objc_setAssociatedObject(cell, "RetainedBlock", block, OBJC_ASSOCIATION_RETAIN);
|
||||
|
||||
[button addTarget:block action:@selector(invoke) forControlEvents:UIControlEventValueChanged];
|
||||
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
}
|
||||
else if (item[@"type"] == typeDisabled) {
|
||||
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
if (@available(iOS 13.0, *)) {
|
||||
cell.textLabel.textColor = [UIColor separatorColor];
|
||||
}
|
||||
else {
|
||||
cell.textLabel.textColor = [UIColor colorWithWhite:0 alpha:0.75];
|
||||
}
|
||||
}
|
||||
else if (item[@"type"] == typeSeparator) {
|
||||
cell.backgroundColor = [UIColor clearColor];
|
||||
cell.separatorInset = UIEdgeInsetsZero;
|
||||
}
|
||||
else if (item[@"type"] == typeSlider ||
|
||||
item[@"type"] == typeLightTemp) {
|
||||
CGRect rect = cell.contentView.bounds;
|
||||
rect.size.width -= 24;
|
||||
rect.size.height -= 24;
|
||||
rect.origin.x += 12;
|
||||
rect.origin.y += 12;
|
||||
UISlider *slider = [item[@"type"] == typeLightTemp? [GBTemperatureSlider alloc] : [UISlider alloc] initWithFrame:rect];
|
||||
slider.continuous = true;
|
||||
slider.minimumValue = [item[@"min"] floatValue];
|
||||
slider.maximumValue = [item[@"max"] floatValue];
|
||||
slider.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
[cell.contentView addSubview:slider];
|
||||
slider.value = [[NSUserDefaults standardUserDefaults] floatForKey:item[@"pref"]];
|
||||
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
|
||||
if (item[@"minImage"] && item[@"maxImage"]) {
|
||||
if ([item[@"minImage"] isKindOfClass:[UIImage class]]) {
|
||||
slider.minimumValueImage = item[@"minImage"];
|
||||
}
|
||||
else if (@available(iOS 13.0, *)) {
|
||||
slider.minimumValueImage = [UIImage systemImageNamed:item[@"minImage"]];
|
||||
}
|
||||
|
||||
if ([item[@"maxImage"] isKindOfClass:[UIImage class]]) {
|
||||
slider.maximumValueImage = item[@"maxImage"];
|
||||
}
|
||||
else if (@available(iOS 13.0, *)) {
|
||||
slider.maximumValueImage = [UIImage systemImageNamed:item[@"maxImage"]];
|
||||
}
|
||||
[GBSettingsViewController fixSliderTint:slider];
|
||||
}
|
||||
|
||||
id block = ^(){
|
||||
[[NSUserDefaults standardUserDefaults] setDouble:slider.value forKey:item[@"pref"]];
|
||||
};
|
||||
objc_setAssociatedObject(cell, "RetainedBlock", block, OBJC_ASSOCIATION_RETAIN);
|
||||
|
||||
[slider addTarget:block action:@selector(invoke) forControlEvents:UIControlEventValueChanged];
|
||||
}
|
||||
|
||||
if ([self followingItemForIndexPath:indexPath][@"type"] == typeSeparator) {
|
||||
cell.separatorInset = UIEdgeInsetsZero;
|
||||
}
|
||||
cell.imageView.image = item[@"image"];
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
NSDictionary *item = [self itemForIndexPath:indexPath];
|
||||
if (item[@"type"] == typeSubmenu || item[@"type"] == typeOptionSubmenu) {
|
||||
UITableViewStyle style = UITableViewStyleGrouped;
|
||||
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
|
||||
if (@available(iOS 13.0, *)) {
|
||||
style = UITableViewStyleInsetGrouped;
|
||||
}
|
||||
}
|
||||
GBSettingsViewController *submenu = [[GBSettingsViewController alloc] initWithStructure:item[@"submenu"]
|
||||
title:item[@"title"]
|
||||
style:style];
|
||||
if (_detailsNavigation) {
|
||||
[_detailsNavigation setViewControllers:@[submenu] animated:false];
|
||||
}
|
||||
else {
|
||||
[self.navigationController pushViewController:submenu animated:true];
|
||||
}
|
||||
return indexPath;
|
||||
}
|
||||
else if (item[@"type"] == typeRadio) {
|
||||
[[NSUserDefaults standardUserDefaults] setObject:item[@"value"] forKey:item[@"pref"]];
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
NSDictionary *item = [self itemForIndexPath:indexPath];
|
||||
if (item[@"type"] == typeSeparator) {
|
||||
return 8;
|
||||
}
|
||||
if (item[@"type"] == typeSlider ||
|
||||
item[@"type"] == typeLightTemp) {
|
||||
return 63;
|
||||
}
|
||||
return [super tableView:tableView heightForRowAtIndexPath:indexPath];
|
||||
|
||||
}
|
||||
|
||||
@end
|
|
@ -1,5 +0,0 @@
|
|||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface GBTableViewCell : UITableViewCell
|
||||
@property double padding;
|
||||
@end
|
|
@ -1,11 +0,0 @@
|
|||
#import "GBTableViewCell.h"
|
||||
|
||||
@implementation GBTableViewCell
|
||||
|
||||
-(void )layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
self.imageView.frame = CGRectInset(self.imageView.frame, 0, self.padding);
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,5 @@
|
|||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface GBTemperatureSlider : UISlider
|
||||
|
||||
@end
|
|
@ -0,0 +1,80 @@
|
|||
#import "GBTemperatureSlider.h"
|
||||
|
||||
static inline void temperature_tint(double temperature, double *r, double *g, double *b)
|
||||
{
|
||||
if (temperature >= 0) {
|
||||
*r = 1;
|
||||
*g = pow(1 - temperature, 0.375);
|
||||
if (temperature >= 0.75) {
|
||||
*b = 0;
|
||||
}
|
||||
else {
|
||||
*b = sqrt(0.75 - temperature) / sqrt(0.75);
|
||||
}
|
||||
}
|
||||
else {
|
||||
*b = 1;
|
||||
double squared = pow(temperature, 2);
|
||||
*g = 0.125 * squared + 0.3 * temperature + 1.0;
|
||||
*r = 0.21875 * squared + 0.5 * temperature + 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
@implementation GBTemperatureSlider
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
[self addTarget:self action:@selector(valueChanged) forControlEvents:UIControlEventValueChanged];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)updateTint
|
||||
{
|
||||
double r, g, b;
|
||||
temperature_tint(self.value, &r, &g, &b);
|
||||
self.thumbTintColor = [UIColor colorWithRed:r green:g blue:b alpha:1.0];
|
||||
}
|
||||
|
||||
- (void)setValue:(float)value
|
||||
{
|
||||
[super setValue:value];
|
||||
[self updateTint];
|
||||
}
|
||||
|
||||
- (void)valueChanged
|
||||
{
|
||||
if (fabsf(self.value) < 0.05 && self.value != 0) {
|
||||
self.value = 0;
|
||||
}
|
||||
else {
|
||||
[self updateTint];
|
||||
}
|
||||
}
|
||||
|
||||
-(UIImage *)maximumTrackImageForState:(UIControlState)state
|
||||
{
|
||||
return [[UIImage alloc] init];
|
||||
}
|
||||
|
||||
|
||||
-(UIImage *)minimumTrackImageForState:(UIControlState)state
|
||||
{
|
||||
return [[UIImage alloc] init];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect
|
||||
{
|
||||
CGSize size = self.bounds.size;
|
||||
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(2, round(size.height / 2 - 1.5), size.width - 4, 3) cornerRadius:4];
|
||||
[path appendPath:[UIBezierPath bezierPathWithRoundedRect:CGRectMake(round(size.width / 2 - 1.5), 12, 3, size.height - 24) cornerRadius:4]];
|
||||
[[UIColor colorWithRed:120 / 255.0
|
||||
green:120 / 255.0
|
||||
blue:130 / 255.0
|
||||
alpha:70 / 255.0] set];
|
||||
[path fill];
|
||||
|
||||
[super drawRect:rect];
|
||||
}
|
||||
|
||||
@end
|
|
@ -7,5 +7,6 @@
|
|||
- (void)start;
|
||||
- (void)stop;
|
||||
- (void)changeModel;
|
||||
- (void)openSettings;
|
||||
- (void)showAbout;
|
||||
@end
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#import "GBMenuViewController.h"
|
||||
#import "GBOptionViewController.h"
|
||||
#import "GBAboutController.h"
|
||||
#import "GBSettingsViewController.h"
|
||||
#include <Core/gb.h>
|
||||
|
||||
@implementation GBViewController
|
||||
|
@ -28,6 +29,7 @@
|
|||
size_t _audioBufferPosition;
|
||||
size_t _audioBufferNeeded;
|
||||
GBAudioClient *_audioClient;
|
||||
NSMutableSet *_defaultsObservers;
|
||||
}
|
||||
|
||||
static void loadBootROM(GB_gameboy_t *gb, GB_boot_rom_t type)
|
||||
|
@ -71,22 +73,60 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
|||
|
||||
- (void)initGameBoy
|
||||
{
|
||||
GB_init(&_gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBCGBModel"]);
|
||||
GB_set_user_data(&_gb, (__bridge void *)(self));
|
||||
GB_set_boot_rom_load_callback(&_gb, (GB_boot_rom_load_callback_t)loadBootROM);
|
||||
GB_set_vblank_callback(&_gb, (GB_vblank_callback_t) vblank);
|
||||
GB_set_log_callback(&_gb, (GB_log_callback_t) consoleLog);
|
||||
GB_set_color_correction_mode(&_gb, (GB_color_correction_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBColorCorrection"]);
|
||||
GB_set_light_temperature(&_gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBLightTemperature"]);
|
||||
GB_set_interference_volume(&_gb, [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBInterferenceVolume"]);
|
||||
GB_set_border_mode(&_gb, GB_BORDER_NEVER);
|
||||
[self updatePalette];
|
||||
GB_set_rgb_encode_callback(&_gb, rgbEncode);
|
||||
GB_set_highpass_filter_mode(&_gb, (GB_highpass_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBHighpassFilter"]);
|
||||
GB_set_rtc_mode(&_gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRTCMode"]);
|
||||
GB_apu_set_sample_callback(&_gb, audioCallback);
|
||||
GB_set_rumble_callback(&_gb, rumbleCallback);
|
||||
[self updateRumbleMode];
|
||||
GB_gameboy_t *gb = &_gb;
|
||||
GB_init(gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBCGBModel"]);
|
||||
GB_set_user_data(gb, (__bridge void *)(self));
|
||||
GB_set_boot_rom_load_callback(gb, (GB_boot_rom_load_callback_t)loadBootROM);
|
||||
GB_set_vblank_callback(gb, (GB_vblank_callback_t) vblank);
|
||||
GB_set_log_callback(gb, (GB_log_callback_t) consoleLog);
|
||||
[self addDefaultObserver:^(id newValue) {
|
||||
GB_set_color_correction_mode(gb, (GB_color_correction_mode_t)[newValue integerValue]);
|
||||
} forKey:@"GBColorCorrection"];
|
||||
[self addDefaultObserver:^(id newValue) {
|
||||
GB_set_light_temperature(gb, [newValue doubleValue]);
|
||||
} forKey:@"GBLightTemperature"];
|
||||
[self addDefaultObserver:^(id newValue) {
|
||||
GB_set_interference_volume(gb, [newValue doubleValue]);
|
||||
} forKey:@"GBInterferenceVolume"];
|
||||
GB_set_border_mode(gb, GB_BORDER_NEVER);
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[self addDefaultObserver:^(id newValue) {
|
||||
[weakSelf updatePalette];
|
||||
} forKey:@"GBCurrentTheme"];
|
||||
GB_set_rgb_encode_callback(gb, rgbEncode);
|
||||
[self addDefaultObserver:^(id newValue) {
|
||||
GB_set_highpass_filter_mode(gb, (GB_highpass_mode_t)[newValue integerValue]);
|
||||
} forKey:@"GB_HIGHPASS_ACCURATE"];
|
||||
[self addDefaultObserver:^(id newValue) {
|
||||
GB_set_rtc_mode(gb, [newValue integerValue]);
|
||||
} forKey:@"GBRTCMode"];
|
||||
GB_apu_set_sample_callback(gb, audioCallback);
|
||||
GB_set_rumble_callback(gb, rumbleCallback);
|
||||
[self addDefaultObserver:^(id newValue) {
|
||||
GB_set_rumble_mode(gb, [newValue integerValue]);
|
||||
} forKey:@"GBRumbleMode"];
|
||||
[self addDefaultObserver:^(id newValue) {
|
||||
GB_set_interference_volume(gb, [newValue doubleValue]);
|
||||
} forKey:@"GBInterferenceVolume"];
|
||||
}
|
||||
|
||||
- (void)addDefaultObserver:(void(^)(id newValue))block forKey:(NSString *)key
|
||||
{
|
||||
if (!_defaultsObservers) {
|
||||
_defaultsObservers = [NSMutableSet set];
|
||||
}
|
||||
block = [block copy];
|
||||
[_defaultsObservers addObject:block];
|
||||
[[NSUserDefaults standardUserDefaults] addObserver:self
|
||||
forKeyPath:key
|
||||
options:NSKeyValueObservingOptionNew
|
||||
context:(void *)block];
|
||||
block([[NSUserDefaults standardUserDefaults] objectForKey:key]);
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
|
||||
{
|
||||
((__bridge void(^)(id))context)(change[NSKeyValueChangeNewKey]);
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
|
@ -111,6 +151,16 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
|||
_gbView.gb = &_gb;
|
||||
[_gbView screenSizeChanged];
|
||||
|
||||
[self addDefaultObserver:^(id newValue) {
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBFilterChanged" object:nil];
|
||||
} forKey:@"GBFilter"];
|
||||
|
||||
__weak GBView *weakGBView = _gbView;
|
||||
[self addDefaultObserver:^(id newValue) {
|
||||
weakGBView.frameBlendingMode = [newValue integerValue];
|
||||
} forKey:@"GBFrameBlendingMode"];
|
||||
|
||||
|
||||
[self willRotateToInterfaceOrientation:[UIApplication sharedApplication].statusBarOrientation
|
||||
duration:0];
|
||||
|
||||
|
@ -221,6 +271,17 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
|||
[self presentViewController:controller animated:true completion:nil];
|
||||
}
|
||||
|
||||
- (void)openSettings
|
||||
{
|
||||
UIBarButtonItem *close = [[UIBarButtonItem alloc] initWithTitle:@"Close"
|
||||
style:UIBarButtonItemStylePlain
|
||||
target:self
|
||||
action:@selector(dismissViewController)];
|
||||
[self presentViewController:[GBSettingsViewController settingsViewControllerWithLeftButton:close]
|
||||
animated:true
|
||||
completion:nil];
|
||||
}
|
||||
|
||||
- (void)showAbout
|
||||
{
|
||||
[self presentViewController:[[GBAboutController alloc] init] animated:true completion:nil];
|
||||
|
@ -383,6 +444,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
|||
|
||||
GB_save_battery(&_gb, [GBROMManager sharedManager].batterySaveFile.fileSystemRepresentation);
|
||||
[self saveStateToFile:[GBROMManager sharedManager].autosaveStateFile];
|
||||
[[GBHapticManager sharedManager] setRumbleStrength:0];
|
||||
}
|
||||
|
||||
- (void)start
|
||||
|
@ -463,32 +525,9 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
|||
[[GBHapticManager sharedManager] setRumbleStrength:amp];
|
||||
}
|
||||
|
||||
- (void)updateRumbleMode
|
||||
{
|
||||
GB_set_rumble_mode(&_gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRumbleMode"]);
|
||||
}
|
||||
|
||||
- (const GB_palette_t *)userPalette
|
||||
{
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
switch ([defaults integerForKey:@"GBColorPalette"]) {
|
||||
case 1: return &GB_PALETTE_DMG;
|
||||
case 2: return &GB_PALETTE_MGB;
|
||||
case 3: return &GB_PALETTE_GBL;
|
||||
default: return &GB_PALETTE_GREY;
|
||||
case -1: {
|
||||
static GB_palette_t customPalette;
|
||||
NSArray *colors = [defaults dictionaryForKey:@"GBThemes"][[defaults stringForKey:@"GBCurrentTheme"]][@"Colors"];
|
||||
if (colors.count == 5) {
|
||||
unsigned i = 0;
|
||||
for (NSNumber *color in colors) {
|
||||
uint32_t c = [color unsignedIntValue];
|
||||
customPalette.colors[i++] = (struct GB_color_s) {c, c >> 8, c >> 16};
|
||||
}
|
||||
}
|
||||
return &customPalette;
|
||||
}
|
||||
}
|
||||
return [GBSettingsViewController paletteForTheme:[[NSUserDefaults standardUserDefaults] stringForKey:@"GBCurrentTheme"]];
|
||||
}
|
||||
|
||||
- (void)updatePalette
|
||||
|
|
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 730 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 574 B |
After Width: | Height: | Size: 1.0 KiB |
|
@ -9,7 +9,8 @@ int main(int argc, char * argv[])
|
|||
[[NSUserDefaults standardUserDefaults] registerDefaults:@{
|
||||
@"GBFilter": @"NearestNeighbor",
|
||||
@"GBColorCorrection": @(GB_COLOR_CORRECTION_MODERN_BALANCED),
|
||||
@"GBHighpassFilter": @(GB_HIGHPASS_REMOVE_DC_OFFSET),
|
||||
@"GBHighpassFilter": @(GB_HIGHPASS_ACCURATE),
|
||||
@"GBRewindLength": @(10),
|
||||
@"GBFrameBlendingMode": @(GB_FRAME_BLENDING_MODE_ACCURATE),
|
||||
|
||||
@"GBDMGModel": @(GB_MODEL_DMG_B),
|
||||
|
@ -17,10 +18,9 @@ int main(int argc, char * argv[])
|
|||
@"GBAGBModel": @(GB_MODEL_AGB_A),
|
||||
@"GBSGBModel": @(GB_MODEL_SGB2),
|
||||
@"GBRumbleMode": @(GB_RUMBLE_CARTRIDGE_ONLY),
|
||||
@"GBColorPalette": @(1),
|
||||
|
||||
@"GBVolume": @(1.0),
|
||||
@"GBButtonHaptics": @YES,
|
||||
|
||||
@"GBCurrentTheme": @"Lime (Game Boy)",
|
||||
// Default themes
|
||||
@"GBThemes": @{
|
||||
@"Desert": @{
|
||||
|
|
After Width: | Height: | Size: 630 B |
After Width: | Height: | Size: 733 B |