mirror of https://github.com/LIJI32/SameBoy.git
Printer emulation in iOS
This commit is contained in:
parent
7127e3e068
commit
a372b2ec0f
|
@ -0,0 +1,5 @@
|
|||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface GBCheckableAlertController : UIAlertController
|
||||
@property UIAlertAction *selectedAction;
|
||||
@end
|
|
@ -0,0 +1,32 @@
|
|||
#import "GBCheckableAlertController.h"
|
||||
|
||||
@implementation GBCheckableAlertController
|
||||
{
|
||||
bool _addedChecks;
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
if (!_addedChecks && _selectedAction) {
|
||||
_addedChecks = true;
|
||||
NSMutableSet *set = [NSMutableSet setWithObject:self.view];
|
||||
while (set.count) {
|
||||
UIView *view = [set anyObject];
|
||||
[set removeObject:view];
|
||||
if ([view.debugDescription containsString:_selectedAction.debugDescription]) {
|
||||
UIImageView *checkImage = [[UIImageView alloc] initWithImage:[UIImage systemImageNamed:@"checkmark"]];
|
||||
CGRect bounds = view.bounds;
|
||||
CGRect frame = checkImage.frame;
|
||||
frame.origin.x = bounds.size.width - frame.size.width - 12;
|
||||
frame.origin.y = round((bounds.size.height - frame.size.height) / 2);
|
||||
checkImage.frame = frame;
|
||||
[view addSubview:checkImage];
|
||||
break;
|
||||
}
|
||||
[set addObjectsFromArray:view.subviews];
|
||||
}
|
||||
}
|
||||
[super viewWillAppear:animated];
|
||||
}
|
||||
|
||||
@end
|
|
@ -54,27 +54,30 @@ static NSString *const tips[] = {
|
|||
NSString *label;
|
||||
NSString *image;
|
||||
NSString *selector;
|
||||
bool requireRunning;
|
||||
} buttons[] = {
|
||||
{@"Reset", @"arrow.2.circlepath", SelectorString(reset)},
|
||||
{@"Reset", @"arrow.2.circlepath", SelectorString(reset), true},
|
||||
{@"Library", @"bookmark", SelectorString(openLibrary)},
|
||||
{@"Connect", @"LinkCableTemplate", SelectorString(openConnectMenu), true},
|
||||
{@"Model", @"ModelTemplate", SelectorString(changeModel)},
|
||||
{@"States", @"square.stack", SelectorString(openStates)},
|
||||
{@"States", @"square.stack", SelectorString(openStates), true},
|
||||
{@"Cheats", @"wand.and.stars", nil}, // TODO
|
||||
{@"Settings", @"gear", SelectorString(openSettings)},
|
||||
{@"About", @"info.circle", SelectorString(showAbout)},
|
||||
};
|
||||
|
||||
double width = self.view.frame.size.width / 3;
|
||||
double width = self.view.frame.size.width / 4;
|
||||
double height = 88;
|
||||
for (unsigned i = 0; i < 6; i++) {
|
||||
unsigned x = i % 3;
|
||||
unsigned y = i / 3;
|
||||
for (unsigned i = 0; i < 8; i++) {
|
||||
unsigned x = i % 4;
|
||||
unsigned y = i / 4;
|
||||
GBMenuButton *button = [GBMenuButton buttonWithType:UIButtonTypeSystem];
|
||||
[button setTitle:buttons[i].label forState:UIControlStateNormal];
|
||||
if (@available(iOS 13.0, *)) {
|
||||
UIImage *image = [UIImage imageNamed:buttons[i].image] ?: [UIImage systemImageNamed:buttons[i].image];
|
||||
[button setImage:image forState:UIControlStateNormal];
|
||||
}
|
||||
button.frame = CGRectMake(width * x, height * y, width, height);
|
||||
button.frame = CGRectMake(round(width * x), height * y, round(width), height);
|
||||
button.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
|
||||
[self.view addSubview:button];
|
||||
|
||||
|
@ -82,9 +85,12 @@ static NSString *const tips[] = {
|
|||
button.enabled = false;
|
||||
continue;
|
||||
}
|
||||
if (buttons[i].selector == nil) {
|
||||
button.enabled = false;
|
||||
continue;
|
||||
}
|
||||
SEL selector = NSSelectorFromString(buttons[i].selector);
|
||||
if ((selector == @selector(reset) || selector == @selector(openStates))
|
||||
&& ![GBROMManager sharedManager].currentROM) {
|
||||
if (buttons[i].requireRunning && ![GBROMManager sharedManager].currentROM) {
|
||||
button.enabled = false;
|
||||
continue;
|
||||
}
|
||||
|
@ -149,8 +155,32 @@ static NSString *const tips[] = {
|
|||
|
||||
- (void)viewDidLayoutSubviews
|
||||
{
|
||||
if (self.view.bounds.size.height < 88 * 2) {
|
||||
[self.view.heightAnchor constraintEqualToConstant:self.view.bounds.size.height + 88 * 2].active = true;
|
||||
CGRect frame = self.view.frame;
|
||||
if (frame.size.height < 88 * 2) {
|
||||
[self.view.heightAnchor constraintEqualToConstant:frame.size.height + 88 * 2].active = true;
|
||||
}
|
||||
double width = MIN(MIN(UIScreen.mainScreen.bounds.size.width, UIScreen.mainScreen.bounds.size.height) - 16, 400);
|
||||
/* Damn I hate NSLayoutConstraints */
|
||||
if (frame.size.width != width) {
|
||||
for (UIView *subview in self.view.subviews) {
|
||||
if (![subview isKindOfClass:[GBMenuButton class]]) {
|
||||
for (NSLayoutConstraint *constraint in subview.constraints) {
|
||||
if (constraint.constant == frame.size.width) {
|
||||
constraint.active = false;
|
||||
}
|
||||
}
|
||||
[subview.widthAnchor constraintEqualToConstant:width].active = true;
|
||||
for (UIView *subsubview in subview.subviews) {
|
||||
for (NSLayoutConstraint *constraint in subsubview.constraints) {
|
||||
if (constraint.constant == frame.size.width) {
|
||||
constraint.active = false;
|
||||
}
|
||||
}
|
||||
[subsubview.widthAnchor constraintEqualToConstant:width].active = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
[self.view.widthAnchor constraintEqualToConstant:width].active = true;
|
||||
}
|
||||
[self layoutTip];
|
||||
[super viewDidLayoutSubviews];
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface GBPrinterFeedController : UINavigationController
|
||||
- (instancetype)initWithImage:(UIImage *)image;
|
||||
- (void)emptyPrinterFeed;
|
||||
@end
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
#import "GBPrinterFeedController.h"
|
||||
#import "GBViewController.h"
|
||||
|
||||
@implementation GBPrinterFeedController
|
||||
{
|
||||
UIViewController *_scrollViewController;
|
||||
UIImage *_image;
|
||||
}
|
||||
|
||||
- (instancetype)initWithImage:(UIImage *)image
|
||||
{
|
||||
_image = image;
|
||||
_scrollViewController = [[UIViewController alloc] init];
|
||||
_scrollViewController.title = @"Printer Feed";
|
||||
_scrollViewController.view.backgroundColor = [UIColor systemBackgroundColor];
|
||||
|
||||
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:_scrollViewController.view.bounds];
|
||||
|
||||
scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
scrollView.scrollEnabled = true;
|
||||
scrollView.pagingEnabled = false;
|
||||
scrollView.showsVerticalScrollIndicator = true;
|
||||
scrollView.showsHorizontalScrollIndicator = false;
|
||||
[_scrollViewController.view addSubview:scrollView];
|
||||
|
||||
CGSize size = image.size;
|
||||
while (size.width < 320) {
|
||||
size.width *= 2;
|
||||
size.height *= 2;
|
||||
}
|
||||
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
|
||||
imageView.contentMode = UIViewContentModeScaleToFill;
|
||||
imageView.frame = (CGRect){{0, 0}, size};
|
||||
imageView.layer.magnificationFilter = kCAFilterNearest;
|
||||
|
||||
scrollView.contentSize = size;
|
||||
[scrollView addSubview:imageView];
|
||||
|
||||
CGSize contentSize = size;
|
||||
self.preferredContentSize = contentSize;
|
||||
|
||||
self = [self initWithRootViewController:_scrollViewController];
|
||||
UIBarButtonItem *close = [[UIBarButtonItem alloc] initWithTitle:@"Close"
|
||||
style:UIBarButtonItemStylePlain
|
||||
target:self
|
||||
action:@selector(dismissFromParent)];
|
||||
[self.visibleViewController.navigationItem setLeftBarButtonItem:close];
|
||||
[_scrollViewController setToolbarItems:@[
|
||||
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction
|
||||
target:self
|
||||
action:@selector(presentShareSheet)],
|
||||
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
|
||||
target:nil
|
||||
action:nil],
|
||||
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash
|
||||
target:self
|
||||
action:@selector(emptyFeed)],
|
||||
|
||||
] animated:false];
|
||||
[self setToolbarHidden:false animated:false];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)viewWillLayoutSubviews
|
||||
{
|
||||
[super viewWillLayoutSubviews];
|
||||
if ([UIDevice currentDevice].userInterfaceIdiom != UIUserInterfaceIdiomPad) {
|
||||
CGRect frame = self.view.frame;
|
||||
frame.origin.x = ([UIScreen mainScreen].bounds.size.width - 320) / 2;
|
||||
frame.size.width = 320;
|
||||
self.view.frame = frame;
|
||||
|
||||
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:self.view.bounds
|
||||
byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight
|
||||
cornerRadii:CGSizeMake(12.0, 12.0)];
|
||||
|
||||
CAShapeLayer *maskLayer = [CAShapeLayer layer];
|
||||
maskLayer.frame = self.view.bounds;
|
||||
maskLayer.path = maskPath.CGPath;
|
||||
|
||||
self.view.layer.mask = maskLayer;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)presentShareSheet
|
||||
{
|
||||
NSURL *url = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"Game Boy Printer Image.png"]];
|
||||
[UIImagePNGRepresentation(_image) writeToURL:url atomically:false];
|
||||
[self presentViewController:[[UIActivityViewController alloc] initWithActivityItems:@[url]
|
||||
applicationActivities:nil]
|
||||
animated:true
|
||||
completion:nil];
|
||||
}
|
||||
|
||||
- (void)emptyFeed
|
||||
{
|
||||
[(GBViewController *)UIApplication.sharedApplication.delegate emptyPrinterFeed];
|
||||
}
|
||||
|
||||
- (void)dismissFromParent
|
||||
{
|
||||
[self.presentingViewController dismissViewControllerAnimated:true completion:nil];
|
||||
}
|
||||
|
||||
- (UIModalPresentationStyle)modalPresentationStyle
|
||||
{
|
||||
return UIModalPresentationFormSheet;
|
||||
}
|
||||
@end
|
|
@ -22,6 +22,8 @@ typedef enum {
|
|||
- (void)openStates;
|
||||
- (void)openSettings;
|
||||
- (void)showAbout;
|
||||
- (void)openConnectMenu;
|
||||
- (void)emptyPrinterFeed;
|
||||
- (void)saveStateToFile:(NSString *)file;
|
||||
- (bool)loadStateFromFile:(NSString *)file;
|
||||
- (bool)handleOpenURLs:(NSArray <NSURL *> *)urls openInPlace:(bool)inPlace;
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#import "GBAboutController.h"
|
||||
#import "GBSettingsViewController.h"
|
||||
#import "GBStatesViewController.h"
|
||||
#import "GBCheckableAlertController.h"
|
||||
#import "GBPrinterFeedController.h"
|
||||
#import "GCExtendedGamepad+AllElements.h"
|
||||
#import "GBZipReader.h"
|
||||
#import <sys/stat.h>
|
||||
|
@ -67,6 +69,11 @@
|
|||
|
||||
UIWindow *_mirrorWindow;
|
||||
GBView *_mirrorView;
|
||||
|
||||
bool _printerConnected;
|
||||
UIButton *_printerButton;
|
||||
UIActivityIndicatorView *_printerSpinner;
|
||||
NSMutableData *_currentPrinterImageData;
|
||||
}
|
||||
|
||||
static void loadBootROM(GB_gameboy_t *gb, GB_boot_rom_t type)
|
||||
|
@ -81,6 +88,21 @@ static void vblank(GB_gameboy_t *gb, GB_vblank_type_t type)
|
|||
[self vblankWithType:type];
|
||||
}
|
||||
|
||||
|
||||
static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
|
||||
uint8_t top_margin, uint8_t bottom_margin, uint8_t exposure)
|
||||
{
|
||||
GBViewController *self = (__bridge GBViewController *)GB_get_user_data(gb);
|
||||
[self printImage:image height:height topMargin:top_margin bottomMargin:bottom_margin exposure:exposure];
|
||||
}
|
||||
|
||||
static void printDone(GB_gameboy_t *gb)
|
||||
{
|
||||
GBViewController *self = (__bridge GBViewController *)GB_get_user_data(gb);
|
||||
[self printDone];
|
||||
}
|
||||
|
||||
|
||||
static void consoleLog(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes)
|
||||
{
|
||||
static NSString *buffer = @"";
|
||||
|
@ -350,7 +372,33 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
|||
object:nil];
|
||||
}
|
||||
|
||||
_printerButton = [[UIButton alloc] init];
|
||||
_printerSpinner = [[UIActivityIndicatorView alloc] init];
|
||||
[self didRotateFromInterfaceOrientation:[UIApplication sharedApplication].statusBarOrientation];
|
||||
|
||||
if (@available(iOS 13.0, *)) {
|
||||
[_printerButton setImage:[UIImage systemImageNamed:@"printer"
|
||||
withConfiguration:[UIImageSymbolConfiguration configurationWithScale:UIImageSymbolScaleLarge]]
|
||||
forState:UIControlStateNormal];
|
||||
_printerButton.backgroundColor = [UIColor systemBackgroundColor];
|
||||
}
|
||||
else {
|
||||
UIImage *rotateImage = [[UIImage imageNamed:@"PrinterTemplate"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
|
||||
[_printerButton setImage:rotateImage
|
||||
forState:UIControlStateNormal];
|
||||
_printerButton.backgroundColor = [UIColor whiteColor];
|
||||
}
|
||||
|
||||
_printerButton.layer.cornerRadius = 6;
|
||||
_printerButton.alpha = 0;
|
||||
[_printerButton addTarget:self
|
||||
action:@selector(showPrinterFeed)
|
||||
forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
|
||||
[_backgroundView addSubview:_printerButton];
|
||||
[_backgroundView addSubview:_printerSpinner];
|
||||
|
||||
|
||||
[self updateMirrorWindow];
|
||||
|
||||
|
@ -835,6 +883,16 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
|||
32,
|
||||
32);
|
||||
}
|
||||
_printerButton.frame = CGRectMake(_backgroundView.bounds.size.width - 8 - insets.right - 32,
|
||||
_backgroundView.bounds.size.height - 8 - insets.bottom - 32,
|
||||
32,
|
||||
32);
|
||||
|
||||
_printerSpinner.frame = CGRectMake(_backgroundView.bounds.size.width - 8 - insets.right - 32,
|
||||
_backgroundView.bounds.size.height - 8 - insets.bottom - 32 - 32 - 8,
|
||||
32,
|
||||
32);
|
||||
|
||||
}
|
||||
|
||||
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
|
||||
|
@ -1584,4 +1642,85 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
|
|||
});
|
||||
}
|
||||
|
||||
- (void)openConnectMenu
|
||||
{
|
||||
UIAlertControllerStyle style = [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad?
|
||||
UIAlertControllerStyleAlert : UIAlertControllerStyleActionSheet;
|
||||
GBCheckableAlertController *menu = [GBCheckableAlertController alertControllerWithTitle:@"Connect which accessory?"
|
||||
message:nil
|
||||
preferredStyle:style];
|
||||
[menu addAction:[UIAlertAction actionWithTitle:@"None"
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction *action) {
|
||||
_printerConnected = false;
|
||||
_currentPrinterImageData = nil;
|
||||
[UIView animateWithDuration:0.25 animations:^{
|
||||
_printerButton.alpha = 0;
|
||||
}];
|
||||
[_printerSpinner stopAnimating];
|
||||
GB_disconnect_serial(&_gb);
|
||||
}]];
|
||||
[menu addAction:[UIAlertAction actionWithTitle:@"Game Boy Printer"
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction *action) {
|
||||
_printerConnected = true;
|
||||
GB_connect_printer(&_gb, printImage, printDone);
|
||||
}]];
|
||||
menu.selectedAction = menu.actions[_printerConnected];
|
||||
[menu addAction:[UIAlertAction actionWithTitle:@"Cancel"
|
||||
style:UIAlertActionStyleCancel
|
||||
handler:nil]];
|
||||
[self presentViewController:menu animated:true completion:nil];
|
||||
}
|
||||
|
||||
- (void)printImage:(uint32_t *)imageBytes height:(unsigned) height
|
||||
topMargin:(unsigned) topMargin bottomMargin: (unsigned) bottomMargin
|
||||
exposure:(unsigned) exposure
|
||||
{
|
||||
uint32_t paddedImage[160 * (topMargin + height + bottomMargin)];
|
||||
memset(paddedImage, 0xFF, sizeof(paddedImage));
|
||||
memcpy(paddedImage + (160 * topMargin), imageBytes, 160 * height * sizeof(imageBytes[0]));
|
||||
if (!_currentPrinterImageData) {
|
||||
_currentPrinterImageData = [[NSMutableData alloc] init];
|
||||
}
|
||||
[_currentPrinterImageData appendBytes:paddedImage length:sizeof(paddedImage)];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[UIView animateWithDuration:0.25 animations:^{
|
||||
_printerButton.alpha = 1;
|
||||
}];
|
||||
[_printerSpinner startAnimating];
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
- (void)printDone
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[_printerSpinner stopAnimating];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)showPrinterFeed
|
||||
{
|
||||
UIImage *image = [self imageFromData:_currentPrinterImageData
|
||||
width:160
|
||||
height:_currentPrinterImageData.length / 160 / sizeof(uint32_t)];
|
||||
|
||||
[self presentViewController:[[GBPrinterFeedController alloc] initWithImage:image]
|
||||
animated:true
|
||||
completion:nil];
|
||||
|
||||
}
|
||||
|
||||
- (void)emptyPrinterFeed
|
||||
{
|
||||
_currentPrinterImageData = nil;
|
||||
[UIView animateWithDuration:0.25 animations:^{
|
||||
_printerButton.alpha = 0;
|
||||
}];
|
||||
[_printerSpinner stopAnimating];
|
||||
[self dismissViewController];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 561 B |
Binary file not shown.
After Width: | Height: | Size: 851 B |
Binary file not shown.
After Width: | Height: | Size: 405 B |
Binary file not shown.
After Width: | Height: | Size: 582 B |
Loading…
Reference in New Issue