SameBoy | Accurate GB/GBC emulator |
| download: https://git.y1.nz/archives/sameboy.tar.gz | |
| README | Files | Log | Refs | LICENSE |
iOS/GBTheme.m
1 #import "GBTheme.h"
2 #import "GBVerticalLayout.h"
3 #import "GBHorizontalLayout.h"
4 #import "GBBackgroundView.h"
5
6 @interface GBLazyObject : NSProxy
7 @end
8
9 @implementation GBLazyObject
10 {
11 id _target;
12 id (^_constructor)(void);
13 }
14
15
16 - (instancetype)initWithConstructor:(id (^)(void))constructor
17 {
18 _constructor = constructor;
19 return self;
20 }
21
22 - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
23 {
24 if (GB_likely(!_target)) {
25 _target = _constructor();
26 _constructor = nil;
27 }
28 return [_target methodSignatureForSelector:sel];
29 }
30
31 - (void)forwardInvocation:(NSInvocation *)invocation
32 {
33 if (GB_likely(!_target)) {
34 _target = _constructor();
35 _constructor = nil;
36 }
37 invocation.target = _target;
38 [invocation invoke];
39 }
40
41 - (instancetype)self
42 {
43 if (GB_likely(!_target)) {
44 _target = _constructor();
45 _constructor = nil;
46 }
47 return _target;
48 }
49
50 @end
51
52 #define MakeColor(r, g, b) [UIColor colorWithRed:(r) / 255.0 green:(g) / 255.0 blue:(b) / 255.0 alpha:1.0]
53
54 __attribute__((objc_direct_members))
55 @implementation GBTheme
56 {
57 NSDictionary<NSString *, UIImage *> *_imageOverrides;
58 UIImage *_horizontalPreview;
59 UIImage *_verticalPreview;
60 }
61
62
63 // Assumes the image has a purple hue
64 + (UIImage *)_recolorImage:(UIImage *)image withColor:(UIColor *)color
65 {
66 double scale = image.scale;
67
68 image = [UIImage imageWithCGImage:image.CGImage scale:1.0 orientation:UIImageOrientationUp];
69
70 CIImage *ciImage = [CIImage imageWithCGImage:image.CGImage];
71 CIFilter *filter = [CIFilter filterWithName:@"CIColorMatrix"];
72 double r, g, b;
73 [color getRed:&r green:&g blue:&b alpha:NULL];
74
75 [filter setDefaults];
76 [filter setValue:ciImage forKey:@"inputImage"];
77
78 [filter setValue:[CIVector vectorWithX:r * 1.34 Y:1 - r Z:0 W:0] forKey:@"inputRVector"];
79 [filter setValue:[CIVector vectorWithX:g * 1.34 Y:1 - g Z:0 W:0] forKey:@"inputGVector"];
80 [filter setValue:[CIVector vectorWithX:b * 1.34 Y:1 - b Z:0 W:0] forKey:@"inputBVector"];
81
82 [filter setValue:[CIVector vectorWithX:0 Y:0 Z:0 W:1] forKey:@"inputAVector"];
83
84
85 CIContext *context = [CIContext context];
86 CGImageRef cgImage = [context createCGImage:filter.outputImage fromRect:filter.outputImage.extent];
87 image = [UIImage imageWithCGImage:cgImage scale:scale orientation:0];
88 CGImageRelease(cgImage);
89
90 return image;
91 }
92
93 + (UIImage *)recolorImage:(UIImage *)image withColor:(UIColor *)color
94 {
95 return (id)[[GBLazyObject alloc] initWithConstructor:^id{
96 return [self _recolorImage:image withColor:color];
97 }];
98 }
99
100 - (instancetype)initDefaultTheme
101 {
102 self = [super init];
103
104 _brandColor = [UIColor colorWithRed:0 / 255.0 green:70 / 255.0 blue:141 / 255.0 alpha:1.0];
105 _backgroundGradientTop = [UIColor colorWithRed:192 / 255.0 green:195 / 255.0 blue:199 / 255.0 alpha:1.0];
106 _backgroundGradientBottom = [UIColor colorWithRed:174 / 255.0 green:176 / 255.0 blue:180 / 255.0 alpha:1.0];
107
108 _bezelsGradientTop = [UIColor colorWithWhite:53 / 255.0 alpha:1.0];
109 _bezelsGradientBottom = [UIColor colorWithWhite:45 / 255.0 alpha:1.0];
110
111 _name = @"SameBoy";
112
113 return self;
114 }
115
116 - (void)setupBackgroundWithColor:(uint32_t)color
117 {
118 uint8_t r = color >> 16;
119 uint8_t g = color >> 8;
120 uint8_t b = color;
121
122 _backgroundGradientTop = MakeColor(r, g, b);
123 _backgroundGradientBottom = [UIColor colorWithRed:pow(r / 255.0, 1.125) green:pow(g / 255.0, 1.125) blue:pow(b / 255.0, 1.125) alpha:1.0];
124 }
125
126 - (void)setupButtonsWithColor:(UIColor *)color
127 {
128 _imageOverrides = @{
129 @"button": [GBTheme recolorImage:[UIImage imageNamed:@"button"] withColor:color],
130 @"buttonPressed": [GBTheme recolorImage:[UIImage imageNamed:@"buttonPressed"] withColor:color],
131
132 @"dpad": [GBTheme recolorImage:[UIImage imageNamed:@"dpad-tint"] withColor:color],
133 @"swipepad": [GBTheme recolorImage:[UIImage imageNamed:@"swipepad-tint"] withColor:color],
134
135 @"button2": [GBTheme recolorImage:[UIImage imageNamed:@"button2-tint"] withColor:color],
136 @"button2Pressed": [GBTheme recolorImage:[UIImage imageNamed:@"button2Pressed-tint"] withColor:color],
137 };
138 }
139
140 - (instancetype)initDarkTheme
141 {
142 self = [super init];
143
144 [self setupBackgroundWithColor:0x181c23];
145
146 _brandColor = [UIColor colorWithRed:0 / 255.0 green:70 / 255.0 blue:141 / 255.0 alpha:1.0];
147 _bezelsGradientTop = [UIColor colorWithWhite:53 / 255.0 alpha:1.0];
148 _bezelsGradientBottom = [UIColor colorWithWhite:45 / 255.0 alpha:1.0];
149
150 [self setupButtonsWithColor:MakeColor(0x08, 0x0c, 0x12)];
151
152 _name = @"SameBoy Dark";
153
154 return self;
155 }
156
157
158 - (UIColor *)buttonColor
159 {
160 double r, g, b;
161 [_brandColor getRed:&r green:&g blue:&b alpha:NULL];
162 if (r == 1.0 && g == 1.0 && b == 1.0) {
163 return _backgroundGradientTop;
164 }
165 return [UIColor colorWithRed:r green:g blue:b alpha:1.0];
166 }
167
168 - (bool)isDark
169 {
170 double r, g, b;
171 [_backgroundGradientTop getRed:&r green:&g blue:&b alpha:NULL];
172 if (r > 0.25) return false;
173 if (g > 0.25) return false;
174 if (b > 0.25) return false;
175 return true;
176 }
177
178 - (UIImage *)imageNamed:(NSString *)name
179 {
180 UIImage *ret = _imageOverrides[name].self ?: [UIImage imageNamed:name];
181 if (!ret) {
182 if ([name isEqual:@"buttonA"] || [name isEqual:@"buttonB"]) {
183 return [self imageNamed:@"button"];
184 }
185 if ([name isEqual:@"buttonAPressed"] || [name isEqual:@"buttonBPressed"]) {
186 return [self imageNamed:@"buttonPressed"];
187 }
188 }
189 return ret;
190 }
191
192 - (UIImage *)horizontalPreview
193 {
194 if (_horizontalPreview) return _horizontalPreview;
195 _renderingPreview = true;
196 GBLayout *layout = [[GBHorizontalLayout alloc] initWithTheme:self cutoutOnRight:false];
197 _renderingPreview = false;
198 GBBackgroundView *view = [[GBBackgroundView alloc] initWithLayout:layout];
199 [view enterPreviewMode:false];
200 view.usesSwipePad = [[NSUserDefaults standardUserDefaults] boolForKey:@"GBSwipePad"];
201 view.layout = layout;
202 view.bounds = CGRectMake(0, 0,
203 MAX(UIScreen.mainScreen.bounds.size.width, UIScreen.mainScreen.bounds.size.height),
204 MIN(UIScreen.mainScreen.bounds.size.width, UIScreen.mainScreen.bounds.size.height));
205 UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:(CGSize){view.bounds.size.width / 8,
206 view.bounds.size.height / 8,
207 }];
208 return _horizontalPreview = [renderer imageWithActions:^(UIGraphicsImageRendererContext *rendererContext) {
209 CGContextScaleCTM(UIGraphicsGetCurrentContext(), 1 / 8.0, 1 / 8.0);
210 [view.layer renderInContext:rendererContext.CGContext];
211 }];
212 }
213
214 - (UIImage *)verticalPreview
215 {
216 if (_verticalPreview) return _verticalPreview;
217 _renderingPreview = true;
218 GBLayout *layout = [[GBVerticalLayout alloc] initWithTheme:self];
219 _renderingPreview = false;
220 GBBackgroundView *view = [[GBBackgroundView alloc] initWithLayout:layout];
221 [view enterPreviewMode:false];
222 view.usesSwipePad = [[NSUserDefaults standardUserDefaults] boolForKey:@"GBSwipePad"];
223 view.layout = layout;
224 view.bounds = CGRectMake(0, 0,
225 MIN(UIScreen.mainScreen.bounds.size.width, UIScreen.mainScreen.bounds.size.height),
226 MAX(UIScreen.mainScreen.bounds.size.width, UIScreen.mainScreen.bounds.size.height));
227 UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:(CGSize){view.bounds.size.width / 8,
228 view.bounds.size.height / 8,
229 }];
230 return _verticalPreview = [renderer imageWithActions:^(UIGraphicsImageRendererContext *rendererContext) {
231 CGContextScaleCTM(UIGraphicsGetCurrentContext(), 1 / 8.0, 1 / 8.0);
232 [view.layer renderInContext:rendererContext.CGContext];
233 }];
234 }
235
236 @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.