#define GBLayoutInternal #import "GBLayout.h" static double StatusBarHeight(void) { static double ret = 0; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ @autoreleasepool { UIWindow *window = [[UIWindow alloc] init]; [window makeKeyAndVisible]; UIEdgeInsets insets = window.safeAreaInsets; ret = MAX(MAX(insets.left, insets.right), MAX(insets.top, insets.bottom)) ?: 20; [window setHidden:true]; if (!ret && [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) { ret = 32; // iPadOS is buggy af } NSLog(@"height = %f", ret); } }); return ret; } static bool HasHomeBar(void) { static bool ret = false; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ ret = [UIApplication sharedApplication].windows[0].safeAreaInsets.bottom; }); return ret; } @implementation GBLayout { bool _isRenderingMask; } - (instancetype)initWithTheme:(GBTheme *)theme { self = [super init]; if (!self) return nil; _theme = theme; _factor = [UIScreen mainScreen].scale; _resolution = [UIScreen mainScreen].bounds.size; _resolution.width *= _factor; _resolution.height *= _factor; if (_resolution.width > _resolution.height) { _resolution = (CGSize){_resolution.height, _resolution.width}; } _minY = StatusBarHeight() * _factor; _cutout = (_minY <= 24 * _factor || [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)? 0 : _minY; if (HasHomeBar()) { _homeBar = 21 * _factor; } // The Plus series will scale things lossily anyway, so no need to bother with integer scale things // This also "catches" zoomed display modes _hasFractionalPixels = _factor != [UIScreen mainScreen].nativeScale; return self; } - (CGRect)viewRectForOrientation:(UIInterfaceOrientation)orientation { return CGRectMake(0, 0, self.background.size.width / self.factor, self.background.size.height / self.factor); } - (void)drawBackground { CGContextRef context = UIGraphicsGetCurrentContext(); CGColorRef top = _theme.backgroundGradientTop.CGColor; CGColorRef bottom = _theme.backgroundGradientBottom.CGColor; CGColorRef colors[] = {top, bottom}; CFArrayRef colorsArray = CFArrayCreate(NULL, (const void **)colors, 2, &kCFTypeArrayCallBacks); CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB(); CGGradientRef gradient = CGGradientCreateWithColors(colorspace, colorsArray, NULL); CGContextDrawLinearGradient(context, gradient, (CGPoint){0, 0}, (CGPoint){0, self.size.height}, 0); CFRelease(gradient); CFRelease(colorsArray); CFRelease(colorspace); } - (void)drawScreenBezels { CGContextRef context = UIGraphicsGetCurrentContext(); CGColorRef top = _theme.bezelsGradientTop.CGColor; CGColorRef bottom = _theme.bezelsGradientBottom.CGColor; CGColorRef colors[] = {top, bottom}; CFArrayRef colorsArray = CFArrayCreate(NULL, (const void **)colors, 2, &kCFTypeArrayCallBacks); double borderWidth = MIN(self.screenRect.size.width / 40, 16 * _factor); CGRect bezelRect = self.screenRect; bezelRect.origin.x -= borderWidth; bezelRect.origin.y -= borderWidth; bezelRect.size.width += borderWidth * 2; bezelRect.size.height += borderWidth * 2; if (bezelRect.origin.y + bezelRect.size.height >= self.size.height - _homeBar) { bezelRect.origin.y = -32; bezelRect.size.height = self.size.height + 32; } UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:bezelRect cornerRadius:borderWidth]; CGContextSaveGState(context); CGContextSetShadowWithColor(context, (CGSize){0, _factor}, _factor, [UIColor colorWithWhite:1 alpha:0.25].CGColor); [_theme.backgroundGradientBottom setFill]; [path fill]; [path addClip]; CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB(); CGGradientRef gradient = CGGradientCreateWithColors(colorspace, colorsArray, NULL); CGContextDrawLinearGradient(context, gradient, bezelRect.origin, (CGPoint){bezelRect.origin.x, bezelRect.origin.y + bezelRect.size.height}, 0); CGContextSetShadowWithColor(context, (CGSize){0, _factor}, _factor, [UIColor colorWithWhite:0 alpha:0.25].CGColor); path.usesEvenOddFillRule = true; [path appendPath:[UIBezierPath bezierPathWithRect:(CGRect){{0, 0}, self.size}]]; [path fill]; CGContextRestoreGState(context); CGContextSaveGState(context); CGContextSetShadowWithColor(context, (CGSize){0, 0}, borderWidth / 4, [UIColor colorWithWhite:0 alpha:0.125].CGColor); [[UIColor blackColor] setFill]; UIRectFill(self.screenRect); CGContextRestoreGState(context); CFRelease(gradient); CFRelease(colorsArray); CFRelease(colorspace); } - (void)drawLogoInVerticalRange:(NSRange)range controlPadding:(double)padding { UIFont *font = [UIFont fontWithName:@"AvenirNext-BoldItalic" size:range.length * 4 / 3]; CGRect rect = CGRectMake(0, range.location - range.length / 3, self.size.width, range.length * 2); if (self.size.width > self.size.height) { rect.origin.x += _cutout / 2; } NSMutableParagraphStyle *style = [NSParagraphStyle defaultParagraphStyle].mutableCopy; style.alignment = NSTextAlignmentCenter; [@"SAMEBOY" drawInRect:rect withAttributes:@{ NSFontAttributeName: font, NSForegroundColorAttributeName:_isRenderingMask? [UIColor whiteColor] : _theme.brandColor, NSParagraphStyleAttributeName: style, }]; _logoRect = (CGRect){ {(self.size.width - _screenRect.size.width) / 2 + padding, rect.origin.y}, {_screenRect.size.width - padding * 2, rect.size.height} }; } - (void)drawThemedLabelsWithBlock:(void (^)(void))block { // Start with a normal normal pass block(); } - (void)drawRotatedLabel:(NSString *)label withFont:(UIFont *)font origin:(CGPoint)origin distance:(double)distance { CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSaveGState(context); CGContextConcatCTM(context, CGAffineTransformMakeTranslation(origin.x, origin.y)); CGContextConcatCTM(context, CGAffineTransformMakeRotation(-M_PI / 6)); NSMutableParagraphStyle *style = [NSParagraphStyle defaultParagraphStyle].mutableCopy; style.alignment = NSTextAlignmentCenter; [label drawInRect:CGRectMake(-256, distance, 512, 256) withAttributes:@{ NSFontAttributeName: font, NSForegroundColorAttributeName:_isRenderingMask? [UIColor whiteColor] : _theme.brandColor, NSParagraphStyleAttributeName: style, }]; CGContextRestoreGState(context); } - (void)drawLabels { UIFont *labelFont = [UIFont fontWithName:@"AvenirNext-Bold" size:24 * _factor]; UIFont *smallLabelFont = [UIFont fontWithName:@"AvenirNext-DemiBold" size:20 * _factor]; [self drawRotatedLabel:@"A" withFont:labelFont origin:self.aLocation distance:40 * self.factor]; [self drawRotatedLabel:@"B" withFont:labelFont origin:self.bLocation distance:40 * self.factor]; [self drawRotatedLabel:@"SELECT" withFont:smallLabelFont origin:self.selectLocation distance:24 * self.factor]; [self drawRotatedLabel:@"START" withFont:smallLabelFont origin:self.startLocation distance:24 * self.factor]; } - (CGSize)buttonDeltaForMaxHorizontalDistance:(double)maxDistance { CGSize buttonsDelta = {90 * self.factor, 45 * self.factor}; if (buttonsDelta.width <= maxDistance) { return buttonsDelta; } return (CGSize){maxDistance, floor(sqrt(100 * 100 * self.factor * self.factor - maxDistance * maxDistance))}; } - (CGSize)size { return _resolution; } @end