Swipe controls for rewind, turbo and quick save/load

This commit is contained in:
Lior Halphon 2023-01-25 00:42:55 +02:00
parent e7cce8fdde
commit 8557a2c1ec
8 changed files with 224 additions and 32 deletions

View File

@ -359,7 +359,7 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on)
}
if (self.view.isRewinding) {
rewind = true;
[self.osdView displayText:@"Rewinding..."];
[self.osdView displayText:@"Rewinding"];
}
}

View File

@ -430,13 +430,13 @@ static uint32_t color_to_int(NSColor *color)
[self.updateProgressSpinner startAnimation:nil];
self.updateProgressButton.title = @"Cancel";
self.updateProgressButton.enabled = true;
self.updateProgressLabel.stringValue = @"Downloading update...";
self.updateProgressLabel.stringValue = @"Downloading update";
_updateState = UPDATE_DOWNLOADING;
_updateTask = [[NSURLSession sharedSession] downloadTaskWithURL: [NSURL URLWithString:_updateURL] completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
_updateTask = nil;
dispatch_sync(dispatch_get_main_queue(), ^{
self.updateProgressButton.enabled = false;
self.updateProgressLabel.stringValue = @"Extracting update...";
self.updateProgressLabel.stringValue = @"Extracting update";
_updateState = UPDATE_EXTRACTING;
});
@ -498,7 +498,7 @@ static uint32_t color_to_int(NSColor *color)
- (void)performUpgrade
{
self.updateProgressButton.enabled = false;
self.updateProgressLabel.stringValue = @"Instaling update...";
self.updateProgressLabel.stringValue = @"Instaling update";
_updateState = UPDATE_INSTALLING;
self.updateProgressButton.enabled = false;
[self.updateProgressSpinner startAnimation:nil];

View File

@ -58,7 +58,7 @@
return @NO;
case 2:
return @"Add Cheat...";
return @"Add Cheat";
case 3:
return @"";

View File

@ -297,7 +297,7 @@ static inline NSString *keyEquivalentString(NSMenuItem *item)
}
if (is_button_being_modified && button_being_modified == row) {
return @"Select a new key...";
return @"Select a new key";
}
NSNumber *key = [[NSUserDefaults standardUserDefaults] valueForKey:button_to_preference_name(row, self.playerListButton.selectedTag)];

View File

@ -255,11 +255,11 @@ static const uint8_t workboy_vk_to_key[] = {
}
if ((!analogClockMultiplierValid && clockMultiplier > 1) ||
_turbo || (analogClockMultiplierValid && analogClockMultiplier > 1)) {
[self.osdView displayText:@"Fast forwarding..."];
[self.osdView displayText:@"Fast forwarding"];
}
else if ((!analogClockMultiplierValid && clockMultiplier < 1) ||
(analogClockMultiplierValid && analogClockMultiplier < 1)) {
[self.osdView displayText:@"Slow motion..."];
[self.osdView displayText:@"Slow motion"];
}
[super flip];
}

View File

@ -2,6 +2,8 @@
#import "GBViewMetal.h"
#import "GBHapticManager.h"
#import "GBMenuViewController.h"
#import "GBViewController.h"
#import "GBROMManager.h"
double CGPointSquaredDistance(CGPoint a, CGPoint b)
{
@ -81,7 +83,11 @@ static GB_key_mask_t angleToKeyMask(double angle)
{
NSMutableSet<UITouch *> *_touches;
UITouch *_swipePadTouch;
CGPoint _swipeOrigin;
CGPoint _padSwipeOrigin;
UITouch *_screenTouch;
CGPoint _screenSwipeOrigin;
bool _screenSwiped;
UIImageView *_dpadView;
UIImageView *_dpadShadowView;
UIImageView *_aButtonView;
@ -89,6 +95,11 @@ static GB_key_mask_t angleToKeyMask(double angle)
UIImageView *_startButtonView;
UIImageView *_selectButtonView;
UILabel *_screenLabel;
UIVisualEffectView *_overlayView;
UIView *_overlayViewContents;
NSTimer *_fadeTimer;
GB_key_mask_t _lastMask;
}
@ -124,9 +135,35 @@ static GB_key_mask_t angleToKeyMask(double angle)
[self addSubview:_gbView];
[_dpadView addSubview:_dpadShadowView];
UIVisualEffect *effect = [UIBlurEffect effectWithStyle:(UIBlurEffectStyle)UIBlurEffectStyleDark];
_overlayView = [[UIVisualEffectView alloc] initWithEffect:effect];
_overlayView.frame = CGRectMake(8, 8, 32, 32);
_overlayView.layer.cornerRadius = 12;
_overlayView.layer.masksToBounds = true;
_overlayView.alpha = 0;
if (@available(iOS 13.0, *)) {
_overlayViewContents = [[UIImageView alloc] init];
_overlayViewContents.tintColor = [UIColor whiteColor];
}
else {
_overlayViewContents = [[UILabel alloc] init];
((UILabel *)_overlayViewContents).font = [UIFont systemFontOfSize:UIFont.systemFontSize weight:UIFontWeightMedium];
((UILabel *)_overlayViewContents).textColor = [UIColor whiteColor];
}
_overlayViewContents.frame = CGRectMake(8, 8, 160, 20.5);
[_overlayView.contentView addSubview:_overlayViewContents];
[_gbView addSubview:_overlayView];
return self;
}
- (GBViewController *)viewController
{
return (GBViewController *)[UIApplication sharedApplication].delegate;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
static const double dpadRadius = 75;
@ -136,15 +173,26 @@ static GB_key_mask_t angleToKeyMask(double angle)
dpadLocation.y /= factor;
for (UITouch *touch in touches) {
CGPoint point = [touch locationInView:self];
if (CGRectContainsPoint(self.gbView.frame, point)) {
[self.window.rootViewController presentViewController:[GBMenuViewController menu] animated:true completion:nil];
if (CGRectContainsPoint(self.gbView.frame, point) && !_screenTouch) {
if (self.viewController.runMode != GBRunModeNormal) {
self.viewController.runMode = GBRunModeNormal;
[self fadeOverlayOut];
}
else {
_screenTouch = touch;
_screenSwipeOrigin = point;
_screenSwiped = false;
_overlayView.alpha = 0;
[_fadeTimer invalidate];
_fadeTimer = nil;
}
}
if (_usesSwipePad && !_swipePadTouch) {
if (fabs(point.x - dpadLocation.x) <= dpadRadius &&
fabs(point.y - dpadLocation.y) <= dpadRadius) {
_swipePadTouch = touch;
_swipeOrigin = point;
_padSwipeOrigin = point;
}
}
}
@ -157,14 +205,27 @@ static GB_key_mask_t angleToKeyMask(double angle)
if ([touches containsObject:_swipePadTouch]) {
_swipePadTouch = nil;
}
if ([touches containsObject:_screenTouch]) {
_screenTouch = nil;
if (!_screenSwiped) {
[self.window.rootViewController presentViewController:[GBMenuViewController menu] animated:true completion:nil];
}
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBSwipeLock"]) {
if (self.viewController.runMode != GBRunModeNormal) {
self.viewController.runMode = GBRunModeNormal;
[self fadeOverlayOut];
}
}
}
[_touches minusSet:touches];
[self touchesChanged];
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[_touches minusSet:touches];
[self touchesChanged];
[self touchesEnded:touches withEvent:event];
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
@ -184,16 +245,16 @@ static GB_key_mask_t angleToKeyMask(double angle)
dpadHandled = true;
if (_swipePadTouch) {
CGPoint point = [_swipePadTouch locationInView:self];
double squaredDistance = CGPointSquaredDistance(point, _swipeOrigin);
double squaredDistance = CGPointSquaredDistance(point, _padSwipeOrigin);
if (squaredDistance > 16 * 16) {
double angle = CGPointAngle(point, _swipeOrigin);
double angle = CGPointAngle(point, _padSwipeOrigin);
mask |= angleToKeyMask(angle);
if (squaredDistance > 24 * 24) {
double deltaX = point.x - _swipeOrigin.x;
double deltaY = point.y - _swipeOrigin.y;
double deltaX = point.x - _padSwipeOrigin.x;
double deltaY = point.y - _padSwipeOrigin.y;
double distance = sqrt(squaredDistance);
_swipeOrigin.x = point.x - deltaX / distance * 24;
_swipeOrigin.y = point.y - deltaY / distance * 24;
_padSwipeOrigin.x = point.x - deltaX / distance * 24;
_padSwipeOrigin.y = point.y - deltaY / distance * 24;
}
}
}
@ -201,6 +262,24 @@ static GB_key_mask_t angleToKeyMask(double angle)
for (UITouch *touch in _touches) {
if (touch == _swipePadTouch) continue;
CGPoint point = [touch locationInView:self];
if (touch == _screenTouch) {
if (_screenSwiped) continue;
if (point.x - _screenSwipeOrigin.x > 32) {
[self turboSwipe];
}
else if (point.x - _screenSwipeOrigin.x < -32) {
[self rewindSwipe];
}
else if (point.y - _screenSwipeOrigin.y > 32) {
[self saveSwipe];
}
else if (point.y - _screenSwipeOrigin.y < -32) {
[self loadSwipe];
}
continue;
}
point.x *= factor;
point.y *= factor;
if (CGPointSquaredDistance(point, _layout.aLocation) <= buttonRadiusSquared) {
@ -307,4 +386,82 @@ static GB_key_mask_t angleToKeyMask(double angle)
_dpadView.image = [UIImage imageNamed:usesSwipePad? @"swipepad" : @"dpad"];
}
- (void)displayOverlayWithImage:(NSString *)imageName orTitle:(NSString *)title
{
if (@available(iOS 13.0, *)) {
((UIImageView *)_overlayViewContents).image = [UIImage systemImageNamed:imageName
withConfiguration:[UIImageSymbolConfiguration configurationWithWeight:UIImageSymbolWeightMedium]];
}
else {
((UILabel *)_overlayViewContents).text = title;
}
[_overlayViewContents sizeToFit];
CGRect bounds = _overlayViewContents.bounds;
bounds.origin = (CGPoint){8, 8};
bounds.size.width += 16;
bounds.size.height += 16;
_overlayView.frame = bounds;
_overlayView.alpha = 1.0;
}
- (void)fadeOverlayOut
{
[UIView animateWithDuration:1 animations:^{
_overlayView.alpha = 0;
}];
[_fadeTimer invalidate];
_fadeTimer = nil;
}
- (void)turboSwipe
{
_screenSwiped = true;
[self displayOverlayWithImage:@"forward" orTitle:@"Fast-forwarding…"];
self.viewController.runMode = GBRunModeTurbo;
}
- (void)rewindSwipe
{
_screenSwiped = true;
[self displayOverlayWithImage:@"backward" orTitle:@"Rewinding…"];
self.viewController.runMode = GBRunModeRewind;
}
- (NSString *)swipeStateFile
{
return [[GBROMManager sharedManager] stateFile:1];
}
- (void)saveSwipe
{
_screenSwiped = true;
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBSwipeState"]) {
return;
}
[self displayOverlayWithImage:@"square.and.arrow.down" orTitle:@"Saved state to Slot 1"];
_fadeTimer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:false block:^(NSTimer *timer) {
[self fadeOverlayOut];
}];
[self.viewController stop];
[self.viewController saveStateToFile:self.swipeStateFile];
[self.viewController start];
}
- (void)loadSwipe
{
_screenSwiped = true;
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBSwipeState"]) {
return;
}
[self displayOverlayWithImage:@"square.and.arrow.up" orTitle:@"Loaded state from Slot 1"];
_fadeTimer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:false block:^(NSTimer *timer) {
[self fadeOverlayOut];
}];
[self.viewController stop];
[self.viewController loadStateFromFile:self.swipeStateFile];
[self.viewController start];
}
@end

View File

@ -1,5 +1,12 @@
#import <UIKit/UIKit.h>
typedef enum {
GBRunModeNormal,
GBRunModeTurbo,
GBRunModeRewind,
GBRunModeRewindPaused,
} GBRunMode;
@interface GBViewController : UIViewController <UIApplicationDelegate>
@property (nonatomic, strong) UIWindow *window;
- (void)reset;
@ -12,4 +19,5 @@
- (void)showAbout;
- (void)saveStateToFile:(NSString *)file;
- (void)loadStateFromFile:(NSString *)file;
@property (nonatomic) GBRunMode runMode;
@end

View File

@ -32,6 +32,7 @@
GBAudioClient *_audioClient;
NSMutableSet *_defaultsObservers;
GB_palette_t _palette;
bool _rewind;
}
static void loadBootROM(GB_gameboy_t *gb, GB_boot_rom_t type)
@ -110,6 +111,9 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
[self addDefaultObserver:^(id newValue) {
GB_set_interference_volume(gb, [newValue doubleValue]);
} forKey:@"GBInterferenceVolume"];
[self addDefaultObserver:^(id newValue) {
GB_set_rewind_length(gb, [newValue unsignedIntValue]);
} forKey:@"GBRewindLength"];
}
- (void)addDefaultObserver:(void(^)(id newValue))block forKey:(NSString *)key
@ -185,6 +189,17 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
return true;
}
- (void)saveStateToFile:(NSString *)file
{
GB_save_state(&_gb, file.fileSystemRepresentation);
NSData *data = [NSData dataWithBytes:_gbView.previousBuffer
length:GB_get_screen_width(&_gb) *
GB_get_screen_height(&_gb) *
sizeof(*_gbView.previousBuffer)];
UIImage *screenshot = [self imageFromData:data width:GB_get_screen_width(&_gb) height:GB_get_screen_height(&_gb)];
[UIImagePNGRepresentation(screenshot) writeToFile:[file stringByAppendingPathExtension:@"png"] atomically:false];
}
- (void)loadStateFromFile:(NSString *)file
{
GB_model_t model;
@ -417,7 +432,16 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
{
[self preRun];
while (_running) {
GB_run(&_gb);
if (_rewind) {
_rewind = false;
GB_rewind_pop(&_gb);
if (!GB_rewind_pop(&_gb)) {
self.runMode = GBRunModeRewindPaused;
}
}
if (_runMode != GBRunModeRewindPaused) {
GB_run(&_gb);
}
}
[self postRun];
_stopping = false;
@ -450,17 +474,6 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
return ret;
}
- (void)saveStateToFile:(NSString *)file
{
GB_save_state(&_gb, file.fileSystemRepresentation);
NSData *data = [NSData dataWithBytes:_gbView.previousBuffer
length:GB_get_screen_width(&_gb) *
GB_get_screen_height(&_gb) *
sizeof(*_gbView.previousBuffer)];
UIImage *screenshot = [self imageFromData:data width:GB_get_screen_width(&_gb) height:GB_get_screen_height(&_gb)];
[UIImagePNGRepresentation(screenshot) writeToFile:[file stringByAppendingPathExtension:@"png"] atomically:false];
}
- (void)postRun
{
[_audioLock lock];
@ -519,6 +532,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
[_gbView flip];
GB_set_pixels_output(&_gb, _gbView.pixels);
}
_rewind = _runMode == GBRunModeRewind;
}
- (void)gotNewSample:(GB_sample_t *)sample
@ -580,4 +594,17 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
[self start];
return [GBROMManager sharedManager].currentROM != nil;
}
- (void)setRunMode:(GBRunMode)runMode
{
if (runMode == _runMode) return;
if (_runMode == GBRunModeRewindPaused) {
[_audioClient start];
}
_runMode = runMode;
if (_runMode == GBRunModeRewindPaused) {
[_audioClient stop];
}
GB_set_turbo_mode(&_gb, runMode == GBRunModeTurbo, false);
}
@end