mirror of https://github.com/LIJI32/SameBoy.git
Swipe controls for rewind, turbo and quick save/load
This commit is contained in:
parent
e7cce8fdde
commit
8557a2c1ec
|
@ -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…"];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
return @NO;
|
||||
|
||||
case 2:
|
||||
return @"Add Cheat...";
|
||||
return @"Add Cheat…";
|
||||
|
||||
case 3:
|
||||
return @"";
|
||||
|
|
|
@ -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)];
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue