SameBoy | Accurate GB/GBC emulator |
| download: https://git.y1.nz/archives/sameboy.tar.gz | |
| README | Files | Log | Refs | LICENSE |
commit f7ad80555948c67abee5cbbb3722ef63c2fef1e6 parent 8ea5e8d74db335f5446e27ea05eed96adf4ef234 Author: Lior Halphon <LIJI32@gmail.com> Date: Sun, 22 Jan 2023 22:10:55 +0200 Save states Diffstat:
| M | Makefile | 2 | +- |
| M | iOS/GBLoadROMTableViewController.m | 2 | +- |
| M | iOS/GBMenuViewController.m | 5 | +++-- |
| M | iOS/GBSettingsViewController.m | 6 | ++++++ |
| A | iOS/GBSlotButton.h | 8 | ++++++++ |
| A | iOS/GBSlotButton.m | 150 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | iOS/GBStatesViewController.h | 5 | +++++ |
| A | iOS/GBStatesViewController.m | 140 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | iOS/GBViewController.h | 3 | +++ |
| M | iOS/GBViewController.m | 24 | +++++++++++++++++++----- |
10 files changed, 336 insertions(+), 9 deletions(-)
diff --git a/Makefile b/Makefile @@ -186,7 +186,7 @@ endif CFLAGS += -arch arm64 -miphoneos-version-min=11.0 -isysroot $(SYSROOT) -IAppleCommon LDFLAGS += -arch arm64 OCFLAGS += -x objective-c -fobjc-arc -Wno-deprecated-declarations -isysroot $(SYSROOT) -LDFLAGS += -lobjc -framework UIKit -framework Foundation -framework CoreGraphics -framework Metal -framework MetalKit -framework AudioToolbox -framework AVFoundation -weak_framework CoreHaptics -miphoneos-version-min=11.0 -isysroot $(SYSROOT) +LDFLAGS += -lobjc -framework UIKit -framework Foundation -framework CoreGraphics -framework Metal -framework MetalKit -framework AudioToolbox -framework AVFoundation -framework QuartzCore -weak_framework CoreHaptics -miphoneos-version-min=11.0 -isysroot $(SYSROOT) CODESIGN := codesign -fs - else ifeq ($(PLATFORM),Darwin) diff --git a/iOS/GBLoadROMTableViewController.m b/iOS/GBLoadROMTableViewController.m @@ -44,7 +44,7 @@ [[UIColor tertiaryLabelColor] set]; } else { - [UIColor colorWithWhite:0 alpha:0.5]; + [[UIColor colorWithWhite:0 alpha:0.5] set]; } [mask stroke]; cell.imageView.image = UIGraphicsGetImageFromCurrentImageContext(); diff --git a/iOS/GBMenuViewController.m b/iOS/GBMenuViewController.m @@ -37,7 +37,7 @@ {@"Reset", @"arrow.2.circlepath", SelectorString(reset)}, {@"Library", @"bookmark", SelectorString(openLibrary)}, {@"Model", @"ModelTemplate", SelectorString(changeModel)}, - {@"States", @"square.stack", nil}, + {@"States", @"square.stack", SelectorString(openStates)}, {@"Settings", @"gear", SelectorString(openSettings)}, {@"About", @"info.circle", SelectorString(showAbout)}, }; @@ -62,7 +62,8 @@ continue; } SEL selector = NSSelectorFromString(buttons[i].selector); - if (selector == @selector(reset) && ![GBROMManager sharedManager].currentROM) { + if ((selector == @selector(reset) || selector == @selector(openStates)) + && ![GBROMManager sharedManager].currentROM) { button.enabled = false; continue; } diff --git a/iOS/GBSettingsViewController.m b/iOS/GBSettingsViewController.m @@ -592,4 +592,10 @@ static NSString const *typeLightTemp = @"typeLightTemp"; } +- (void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; + [self.tableView reloadData]; +} + @end diff --git a/iOS/GBSlotButton.h b/iOS/GBSlotButton.h @@ -0,0 +1,8 @@ +#import <UIKit/UIKit.h> + +@interface GBSlotButton : UIButton ++ (instancetype)buttonWithLabelText:(NSString *)label; +@property (readonly) UILabel *label; +@property (readonly) UILabel *subtitleLabel; +@property (nonatomic, getter=isShowingMenu) bool showingMenu; +@end diff --git a/iOS/GBSlotButton.m b/iOS/GBSlotButton.m @@ -0,0 +1,150 @@ +#import "GBSlotButton.h" + +@implementation GBSlotButton +{ + UIImageView *_imageView; +} + ++ (instancetype)buttonWithLabelText:(NSString *)labelText +{ + GBSlotButton *ret = [self buttonWithType:UIButtonTypeCustom]; + if (!ret) return nil; + ret.frame = CGRectMake(0, 0, 0x100, 0x100); + + ret->_subtitleLabel = [[UILabel alloc] init]; + ret->_subtitleLabel.text = @"Empty"; + ret->_subtitleLabel.font = [UIFont systemFontOfSize:UIFont.smallSystemFontSize]; + if (@available(iOS 13.0, *)) { + ret->_subtitleLabel.textColor = [UIColor secondaryLabelColor]; + } + else { + ret->_subtitleLabel.textColor = [UIColor systemGrayColor]; + } + [ret->_subtitleLabel sizeToFit]; + ret->_subtitleLabel.textAlignment = NSTextAlignmentCenter; + CGRect subtitleLabelRect = ret->_subtitleLabel.frame; + subtitleLabelRect.size.width = 0x100; + subtitleLabelRect.origin.y = 0x100 - subtitleLabelRect.size.height - 8; + ret->_subtitleLabel.frame = subtitleLabelRect; + ret->_subtitleLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin; + [ret addSubview:ret->_subtitleLabel]; + + ret->_label = [[UILabel alloc] init]; + ret->_label.text = labelText; + [ret->_label sizeToFit]; + ret->_label.textAlignment = NSTextAlignmentCenter; + CGRect labelRect = ret->_label.frame; + labelRect.size.width = 0x100; + labelRect.origin.y = subtitleLabelRect.origin.y - labelRect.size.height - 4; + ret->_label.frame = labelRect; + ret->_label.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin; + [ret addSubview:ret->_label]; + + ret.backgroundColor = [UIColor clearColor]; + + ret->_imageView = [[UIImageView alloc] initWithImage:nil]; + ret.imageView.layer.cornerRadius = 6; + ret.imageView.layer.masksToBounds = true; + if (@available(iOS 13.0, *)) { + ret.imageView.layer.borderColor = [UIColor tertiaryLabelColor] .CGColor; + } + else { + ret.imageView.layer.borderColor = [UIColor colorWithWhite:0 alpha:0.5].CGColor; + } + ret.imageView.layer.borderWidth = 1; + ret.imageView.layer.backgroundColor = [UIColor whiteColor].CGColor; + [ret addSubview:ret.imageView]; + + return ret; +} + +- (UIImageView *)imageView +{ + return _imageView; +} + +- (void)setHighlighted:(BOOL)highlighted +{ + if (_showingMenu) return; + if (highlighted == self.isHighlighted) return; + [super setHighlighted:highlighted]; + + [UIView animateWithDuration:0.25 animations:^{ + if (highlighted) { + if (@available(iOS 13.0, *)) { + self.backgroundColor = [UIColor tertiarySystemFillColor]; + } + else { + self.backgroundColor = [UIColor colorWithRed:118 / 255.0 + green:118 / 255.0 + blue:128 / 255.0 + alpha:0.12]; + } + self.imageView.layer.opacity = 11 / 12.0; + } + else { + self.backgroundColor = [UIColor clearColor]; + self.imageView.layer.opacity = 1; + } + }]; +} + +- (void)setFrame:(CGRect)frame +{ + [super setFrame:frame]; + CGRect screenshotRect = self.bounds; + screenshotRect.size.width -= 8; + screenshotRect.origin.x += 4; + screenshotRect.size.height = _label.frame.origin.y - 16; + screenshotRect.origin.y += 8; + + double scale = [UIApplication sharedApplication].keyWindow.screen.scale; + double nativeWidth = 160.0 / scale; + double nativeHeight = 144.0 / scale; + + if (screenshotRect.size.width > nativeWidth && + screenshotRect.size.height > nativeHeight) { + self.imageView.layer.magnificationFilter = kCAFilterNearest; + double newWidth = floor(screenshotRect.size.width / nativeWidth) * nativeWidth; + screenshotRect.origin.x += (screenshotRect.size.width - newWidth) / 2; + screenshotRect.size.width = newWidth; + + double newHeight = floor(screenshotRect.size.height / nativeHeight) * nativeHeight; + screenshotRect.origin.y += (screenshotRect.size.height - newHeight) / 2; + screenshotRect.size.height = newHeight; + } + else { + self.imageView.layer.magnificationFilter = kCAFilterLinear; + } + + double aspect = screenshotRect.size.width / screenshotRect.size.height; + if (aspect > 160.0 / 144.0) { + // Too wide + double newWidth = screenshotRect.size.height / 144 * 160; + screenshotRect.origin.x += (screenshotRect.size.width - newWidth) / 2; + screenshotRect.size.width = newWidth; + } + else { + // Too narrow + double newHeight = screenshotRect.size.width / 160 * 144; + screenshotRect.origin.y += (screenshotRect.size.height - newHeight) / 2; + screenshotRect.size.height = newHeight; + } + screenshotRect.origin.x = round(screenshotRect.origin.x); + screenshotRect.origin.y = round(screenshotRect.origin.y); + self.imageView.frame = screenshotRect; +} + +- (void)setShowingMenu:(bool)showingMenu +{ + if (showingMenu) { + self.highlighted = true; + _showingMenu = true; + } + else { + _showingMenu = false; + self.highlighted = false; + } +} + +@end diff --git a/iOS/GBStatesViewController.h b/iOS/GBStatesViewController.h @@ -0,0 +1,5 @@ +#import <UIKit/UIKit.h> + +@interface GBStatesViewController : UIViewController + +@end diff --git a/iOS/GBStatesViewController.m b/iOS/GBStatesViewController.m @@ -0,0 +1,140 @@ +#import "GBStatesViewController.h" +#import "GBSlotButton.h" +#import "GBROMManager.h" +#import "GBViewController.h" + +@interface GBStatesViewController () + +@end + +@implementation GBStatesViewController + +- (void)updateSlotView:(GBSlotButton *)view +{ + NSString *stateFile = [[GBROMManager sharedManager] stateFile:view.tag]; + if ([[NSFileManager defaultManager] fileExistsAtPath:stateFile]) { + NSDate *date = [[[NSFileManager defaultManager] attributesOfItemAtPath:stateFile error:nil] fileModificationDate]; + if (@available(iOS 13.0, *)) { + if ((uint64_t)(date.timeIntervalSince1970) == (uint64_t)([NSDate now].timeIntervalSince1970)) { + view.subtitleLabel.text = @"Just now"; + } + else { + NSRelativeDateTimeFormatter *formatter = [[NSRelativeDateTimeFormatter alloc] init]; + view.subtitleLabel.text = [formatter localizedStringForDate:date relativeToDate:[NSDate now]]; + } + } + else { + NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; + formatter.timeStyle = kCFDateFormatterShortStyle; + formatter.dateStyle = NSDateFormatterShortStyle; + formatter.doesRelativeDateFormatting = true; + view.subtitleLabel.text = [formatter stringFromDate:date]; + } + + view.imageView.image = [UIImage imageWithContentsOfFile:[stateFile stringByAppendingPathExtension:@"png"]]; + } + else { + view.subtitleLabel.text = @"Empty"; + view.imageView.image = nil; + } +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + UIVisualEffect *effect = [UIBlurEffect effectWithStyle:(UIBlurEffectStyle)UIBlurEffectStyleProminent]; + self.view = [[UIVisualEffectView alloc] initWithEffect:effect]; + self.view.bounds = CGRectMake(0, 0, 0x300, 0x300); + UIView *root = ((UIVisualEffectView *)self.view).contentView; + for (unsigned i = 0; i < 9; i++) { + unsigned x = i % 3; + unsigned y = i / 3; + GBSlotButton *slotView = [GBSlotButton buttonWithLabelText:[NSString stringWithFormat:@"Slot %u", i + 1]]; + + slotView.frame = CGRectMake(0x100 * x, + 0x100 * y, + 0x100, + 0x100); + + slotView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | + UIViewAutoresizingFlexibleWidth | + UIViewAutoresizingFlexibleRightMargin | + UIViewAutoresizingFlexibleTopMargin | + UIViewAutoresizingFlexibleHeight | + UIViewAutoresizingFlexibleBottomMargin; + + slotView.tag = i + 1; + [self updateSlotView:slotView]; + [slotView addTarget:self + action:@selector(slotSelected:) + forControlEvents:UIControlEventTouchUpInside]; + [root addSubview:slotView]; + } + self.edgesForExtendedLayout = 0; +} + +- (void)slotSelected:(GBSlotButton *)slot +{ + + UIAlertController *controller = [UIAlertController alertControllerWithTitle:slot.label.text + message:nil + preferredStyle:UIAlertControllerStyleActionSheet]; + + NSString *stateFile = [[GBROMManager sharedManager] stateFile:slot.tag]; + GBViewController *delegate = (typeof(delegate))[UIApplication sharedApplication].delegate; + + void (^saveState)(UIAlertAction *action) = ^(UIAlertAction *action) { + [delegate saveStateToFile:stateFile]; + [self updateSlotView:slot]; + [self.presentingViewController dismissViewControllerAnimated:true completion:nil]; + slot.showingMenu = false; + }; + + if (![[NSFileManager defaultManager] fileExistsAtPath:stateFile]) { + saveState(nil); + return; + } + + [controller addAction:[UIAlertAction actionWithTitle:@"Save state" + style:UIAlertActionStyleDefault + handler:saveState]]; + + [controller addAction:[UIAlertAction actionWithTitle:@"Load state" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + [delegate loadStateFromFile:stateFile]; + [self updateSlotView:slot]; + [self.presentingViewController dismissViewControllerAnimated:true completion:nil]; + slot.showingMenu = false; + }]]; + + [controller addAction:[UIAlertAction actionWithTitle:@"Cancel" + style:UIAlertActionStyleCancel + handler:^(UIAlertAction *action) { + slot.showingMenu = false; + }]]; + + slot.showingMenu = true; + controller.popoverPresentationController.sourceView = slot; + [self presentViewController:controller animated:true completion:nil]; +} + +- (void)viewWillLayoutSubviews +{ + [super viewWillLayoutSubviews]; + if ([UIDevice currentDevice].userInterfaceIdiom != UIUserInterfaceIdiomPad) { + UIView *root = ((UIVisualEffectView *)self.view).contentView; + CGRect frame = root.frame; + UIEdgeInsets insets = [UIApplication sharedApplication].keyWindow.safeAreaInsets; + frame.size.height = self.view.frame.size.height - insets.bottom; + frame.size.width = self.view.frame.size.width - insets.left - insets.right; + frame.origin.x = insets.left; + root.frame = frame; + } +} + +- (NSString *)title +{ + return @"Save States"; +} +@end diff --git a/iOS/GBViewController.h b/iOS/GBViewController.h @@ -7,6 +7,9 @@ - (void)start; - (void)stop; - (void)changeModel; +- (void)openStates; - (void)openSettings; - (void)showAbout; +- (void)saveStateToFile:(NSString *)file; +- (void)loadStateFromFile:(NSString *)file; @end diff --git a/iOS/GBViewController.m b/iOS/GBViewController.m @@ -11,6 +11,7 @@ #import "GBOptionViewController.h" #import "GBAboutController.h" #import "GBSettingsViewController.h" +#import "GBStatesViewController.h" #include <Core/gb.h> @implementation GBViewController @@ -96,7 +97,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) 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"]; + } forKey:@"GBHighpassFilter"]; [self addDefaultObserver:^(id newValue) { GB_set_rtc_mode(gb, [newValue integerValue]); } forKey:@"GBRTCMode"]; @@ -178,12 +179,12 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) return true; } -- (void)loadState:(NSString *)state +- (void)loadStateFromFile:(NSString *)file { GB_model_t model; - if (!GB_get_state_model(state.fileSystemRepresentation, &model)) { + if (!GB_get_state_model(file.fileSystemRepresentation, &model)) { GB_switch_model_and_reset(&_gb, model); - GB_load_state(&_gb, state.fileSystemRepresentation); + GB_load_state(&_gb, file.fileSystemRepresentation); } } @@ -197,7 +198,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) if (_romLoaded) { GB_reset(&_gb); GB_load_battery(&_gb, [GBROMManager sharedManager].batterySaveFile.fileSystemRepresentation); - [self loadState:[GBROMManager sharedManager].autosaveStateFile]; + [self loadStateFromFile:[GBROMManager sharedManager].autosaveStateFile]; } } else { @@ -271,6 +272,19 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) [self presentViewController:controller animated:true completion:nil]; } +- (void)openStates +{ + UINavigationController *controller = [[UINavigationController alloc] initWithRootViewController:[[GBStatesViewController alloc] init]]; + UIBarButtonItem *close = [[UIBarButtonItem alloc] initWithTitle:@"Close" + style:UIBarButtonItemStylePlain + target:self + action:@selector(dismissViewController)]; + [controller.visibleViewController.navigationItem setLeftBarButtonItem:close]; + [self presentViewController:controller + animated:true + completion:nil]; +} + - (void)openSettings { UIBarButtonItem *close = [[UIBarButtonItem alloc] initWithTitle:@"Close"
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.