SameBoy | Accurate GB/GBC emulator |
| download: https://git.y1.nz/archives/sameboy.tar.gz | |
| README | Files | Log | Refs | LICENSE |
iOS/GBLayout.m
1 #define GBLayoutInternal
2 #import "GBLayout.h"
3
4 static double StatusBarHeight(void)
5 {
6 static double ret = 0;
7 static dispatch_once_t onceToken;
8 dispatch_once(&onceToken, ^{
9 @autoreleasepool {
10 UIWindow *window = [[UIWindow alloc] init];
11 [window makeKeyAndVisible];
12 UIEdgeInsets insets = window.safeAreaInsets;
13 ret = MAX(MAX(insets.left, insets.right), MAX(insets.top, insets.bottom)) ?: 20;
14 [window setHidden:true];
15 if (!ret && [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
16 ret = 32; // iPadOS is buggy af
17 }
18 }
19 });
20 return ret;
21 }
22
23 static bool HasHomeBar(void)
24 {
25 static bool ret = false;
26 static dispatch_once_t onceToken;
27 dispatch_once(&onceToken, ^{
28 ret = [UIApplication sharedApplication].windows[0].safeAreaInsets.bottom;
29 });
30 return ret;
31 }
32
33 @implementation GBLayout
34 {
35 bool _isRenderingMask;
36 }
37
38 - (instancetype)initWithTheme:(GBTheme *)theme
39 {
40 self = [super init];
41 if (!self) return nil;
42
43 _theme = theme;
44 _factor = [UIScreen mainScreen].scale;
45 _resolution = [UIScreen mainScreen].bounds.size;
46 _resolution.width *= _factor;
47 _resolution.height *= _factor;
48 if (_resolution.width > _resolution.height) {
49 _resolution = (CGSize){_resolution.height, _resolution.width};
50 }
51
52 _minY = StatusBarHeight() * _factor;
53 _cutout = (_minY <= 24 * _factor || [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)? 0 : _minY;
54
55 if (HasHomeBar()) {
56 _homeBar = 21 * _factor;
57 }
58
59 // The Plus series will scale things lossily anyway, so no need to bother with integer scale things
60 // This also "catches" zoomed display modes
61 _hasFractionalPixels = _factor != [UIScreen mainScreen].nativeScale;
62 return self;
63 }
64
65 - (CGRect)viewRectForOrientation:(UIInterfaceOrientation)orientation
66 {
67 return CGRectMake(0, 0, self.background.size.width / self.factor, self.background.size.height / self.factor);
68 }
69
70 - (void)drawBackground
71 {
72 CGContextRef context = UIGraphicsGetCurrentContext();
73 CGColorRef top = _theme.backgroundGradientTop.CGColor;
74 CGColorRef bottom = _theme.backgroundGradientBottom.CGColor;
75 CGColorRef colors[] = {top, bottom};
76 CFArrayRef colorsArray = CFArrayCreate(NULL, (const void **)colors, 2, &kCFTypeArrayCallBacks);
77
78 CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
79 CGGradientRef gradient = CGGradientCreateWithColors(colorspace, colorsArray, NULL);
80 CGContextDrawLinearGradient(context,
81 gradient,
82 (CGPoint){0, 0},
83 (CGPoint){0, self.size.height},
84 0);
85
86 CFRelease(gradient);
87 CFRelease(colorsArray);
88 CFRelease(colorspace);
89 }
90
91 - (void)drawScreenBezels
92 {
93 CGContextRef context = UIGraphicsGetCurrentContext();
94 CGColorRef top = _theme.bezelsGradientTop.CGColor;
95 CGColorRef bottom = _theme.bezelsGradientBottom.CGColor;
96 CGColorRef colors[] = {top, bottom};
97 CFArrayRef colorsArray = CFArrayCreate(NULL, (const void **)colors, 2, &kCFTypeArrayCallBacks);
98
99 double borderWidth = MIN(self.screenRect.size.width / 40, 16 * _factor);
100 CGRect bezelRect = self.screenRect;
101 bezelRect.origin.x -= borderWidth;
102 bezelRect.origin.y -= borderWidth;
103 bezelRect.size.width += borderWidth * 2;
104 bezelRect.size.height += borderWidth * 2;
105
106 if (bezelRect.origin.y + bezelRect.size.height >= self.size.height - _homeBar) {
107 bezelRect.origin.y = -32;
108 bezelRect.size.height = self.size.height + 32;
109 }
110 UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:bezelRect cornerRadius:borderWidth];
111 CGContextSaveGState(context);
112 CGContextSetShadowWithColor(context, (CGSize){0, _factor}, _factor, [UIColor colorWithWhite:1 alpha:0.25].CGColor);
113 [_theme.backgroundGradientBottom setFill];
114 [path fill];
115 [path addClip];
116
117 CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
118 CGGradientRef gradient = CGGradientCreateWithColors(colorspace, colorsArray, NULL);
119 CGContextDrawLinearGradient(context,
120 gradient,
121 bezelRect.origin,
122 (CGPoint){bezelRect.origin.x, bezelRect.origin.y + bezelRect.size.height},
123 0);
124
125 CGContextSetShadowWithColor(context, (CGSize){0, _factor}, _factor, [UIColor colorWithWhite:0 alpha:0.25].CGColor);
126
127 path.usesEvenOddFillRule = true;
128 [path appendPath:[UIBezierPath bezierPathWithRect:(CGRect){{0, 0}, self.size}]];
129 [path fill];
130
131
132 CGContextRestoreGState(context);
133
134 CGContextSaveGState(context);
135 CGContextSetShadowWithColor(context, (CGSize){0, 0}, borderWidth / 4, [UIColor colorWithWhite:0 alpha:0.125].CGColor);
136
137 [[UIColor blackColor] setFill];
138 UIRectFill(self.screenRect);
139 CGContextRestoreGState(context);
140
141 CFRelease(gradient);
142 CFRelease(colorsArray);
143 CFRelease(colorspace);
144 }
145
146 - (void)drawLogoInVerticalRange:(NSRange)range controlPadding:(double)padding
147 {
148 UIFont *font = [UIFont fontWithName:@"AvenirNext-BoldItalic" size:range.length * 4 / 3];
149
150 CGRect rect = CGRectMake(0,
151 range.location - range.length / 3,
152 self.size.width, range.length * 2);
153 if (self.size.width > self.size.height) {
154 rect.origin.x += _cutout / 2;
155 }
156 NSMutableParagraphStyle *style = [NSParagraphStyle defaultParagraphStyle].mutableCopy;
157 style.alignment = NSTextAlignmentCenter;
158 [@"SAMEBOY" drawInRect:rect
159 withAttributes:@{
160 NSFontAttributeName: font,
161 NSForegroundColorAttributeName:_isRenderingMask? [UIColor whiteColor] : _theme.brandColor,
162 NSParagraphStyleAttributeName: style,
163 }];
164
165 _logoRect = (CGRect){
166 {(self.size.width - _screenRect.size.width) / 2 + padding, rect.origin.y},
167 {_screenRect.size.width - padding * 2, rect.size.height}
168 };
169 }
170
171 - (void)drawThemedLabelsWithBlock:(void (^)(void))block
172 {
173 // Start with a normal normal pass
174 block();
175 }
176
177 - (void)drawRotatedLabel:(NSString *)label withFont:(UIFont *)font origin:(CGPoint)origin distance:(double)distance
178 {
179 CGContextRef context = UIGraphicsGetCurrentContext();
180
181 CGContextSaveGState(context);
182 CGContextConcatCTM(context, CGAffineTransformMakeTranslation(origin.x, origin.y));
183 CGContextConcatCTM(context, CGAffineTransformMakeRotation(-M_PI / 6));
184
185 NSMutableParagraphStyle *style = [NSParagraphStyle defaultParagraphStyle].mutableCopy;
186 style.alignment = NSTextAlignmentCenter;
187
188 [label drawInRect:CGRectMake(-256, distance, 512, 256)
189 withAttributes:@{
190 NSFontAttributeName: font,
191 NSForegroundColorAttributeName:_isRenderingMask? [UIColor whiteColor] : _theme.brandColor,
192 NSParagraphStyleAttributeName: style,
193 }];
194 CGContextRestoreGState(context);
195 }
196
197 - (void)drawLabels
198 {
199
200 UIFont *labelFont = [UIFont fontWithName:@"AvenirNext-Bold" size:24 * _factor];
201 UIFont *smallLabelFont = [UIFont fontWithName:@"AvenirNext-DemiBold" size:20 * _factor];
202
203 [self drawRotatedLabel:@"A" withFont:labelFont origin:self.aLocation distance:40 * self.factor];
204 [self drawRotatedLabel:@"B" withFont:labelFont origin:self.bLocation distance:40 * self.factor];
205 [self drawRotatedLabel:@"SELECT" withFont:smallLabelFont origin:self.selectLocation distance:24 * self.factor];
206 [self drawRotatedLabel:@"START" withFont:smallLabelFont origin:self.startLocation distance:24 * self.factor];
207 }
208
209 - (CGSize)buttonDeltaForMaxHorizontalDistance:(double)maxDistance
210 {
211 CGSize buttonsDelta = {90 * self.factor, 45 * self.factor};
212 if (buttonsDelta.width <= maxDistance) {
213 return buttonsDelta;
214 }
215 return (CGSize){maxDistance, floor(sqrt(100 * 100 * self.factor * self.factor - maxDistance * maxDistance))};
216 }
217
218 - (CGSize)size
219 {
220 return _resolution;
221 }
222 @end
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.