@geastack/apple 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +54 -0
- package/dist/index.d.ts +127 -0
- package/dist/index.js +3076 -0
- package/generated/AVFoundation.d.ts +112 -0
- package/generated/ActivityKit.d.ts +7 -0
- package/generated/AppKit.d.ts +268 -0
- package/generated/CoreGraphics.d.ts +18 -0
- package/generated/CoreLocation.d.ts +28 -0
- package/generated/CoreMedia.d.ts +8 -0
- package/generated/Dispatch.d.ts +2 -0
- package/generated/Foundation.d.ts +13 -0
- package/generated/MapKit.d.ts +28 -0
- package/generated/Metal.d.ts +91 -0
- package/generated/MetalKit.d.ts +22 -0
- package/generated/Photos.d.ts +5 -0
- package/generated/QuartzCore.d.ts +12 -0
- package/generated/SwiftData.d.ts +11 -0
- package/generated/UIKit.d.ts +187 -0
- package/package.json +123 -0
- package/runtime/AVFoundation.js +136 -0
- package/runtime/ActivityKit.js +18 -0
- package/runtime/AppKit.js +313 -0
- package/runtime/CoreGraphics.js +15 -0
- package/runtime/CoreLocation.js +30 -0
- package/runtime/CoreMedia.js +7 -0
- package/runtime/Dispatch.js +11 -0
- package/runtime/Foundation.js +21 -0
- package/runtime/MapKit.js +22 -0
- package/runtime/Metal.js +126 -0
- package/runtime/MetalKit.js +15 -0
- package/runtime/Photos.js +9 -0
- package/runtime/QuartzCore.js +9 -0
- package/runtime/SwiftData.js +29 -0
- package/runtime/UIKit.js +242 -0
- package/runtime/empty.js +1 -0
- package/targets/ios/Info.plist.in +25 -0
- package/targets/ios/README.md +46 -0
- package/targets/ios/build-ios.sh +395 -0
- package/targets/ios/detect-development-team.mjs +109 -0
- package/targets/ios/generate-resident-entry.mjs +67 -0
- package/targets/ios/generate-resident-registry.mjs +162 -0
- package/targets/ios/generate-xcode-project.mjs +520 -0
- package/targets/ios/main/canvas_view.h +7 -0
- package/targets/ios/main/canvas_view.mm +94 -0
- package/targets/ios/main/font_registry.h +17 -0
- package/targets/ios/main/font_registry.mm +129 -0
- package/targets/ios/main/image_bridge.h +13 -0
- package/targets/ios/main/image_bridge.mm +53 -0
- package/targets/ios/main/ios_apps.c +28 -0
- package/targets/ios/main/ios_camera.mm +446 -0
- package/targets/ios/main/ios_display.mm +344 -0
- package/targets/ios/main/ios_main.mm +811 -0
- package/targets/ios/main/ios_memory.cpp +55 -0
- package/targets/ios/main/ios_renderer.h +20 -0
- package/targets/ios/main/ios_renderer.mm +27 -0
- package/targets/ios/main/ios_root_background.cpp +20 -0
- package/targets/ios/main/ios_root_background.h +11 -0
- package/targets/ios/main/ios_sensors.mm +244 -0
- package/targets/ios/main/ios_timers.mm +53 -0
- package/targets/ios/main/renderer/ios_renderer_internal.h +65 -0
- package/targets/ios/main/renderer/ios_renderer_support.mm +111 -0
- package/targets/ios/main/renderer/native_button.mm +179 -0
- package/targets/ios/main/renderer/native_label.mm +161 -0
- package/targets/ios/main/renderer/native_scroll_container.mm +279 -0
- package/targets/ios/main/renderer/view_reconciler.mm +274 -0
- package/targets/macos/Info.plist.in +26 -0
- package/targets/macos/README.md +131 -0
- package/targets/macos/build-child-lifecycle.sh +119 -0
- package/targets/macos/build-macos.sh +2156 -0
- package/targets/macos/check-module-graph-freshness.mjs +82 -0
- package/targets/macos/compiler-input-fingerprint.mjs +32 -0
- package/targets/macos/finalize-apple-native-output.mjs +77 -0
- package/targets/macos/generate-resident-entry.mjs +80 -0
- package/targets/macos/generate-resident-registry.mjs +141 -0
- package/targets/macos/heavy-build-lock.sh +176 -0
- package/targets/macos/main/canvas_view.h +9 -0
- package/targets/macos/main/canvas_view.mm +176 -0
- package/targets/macos/main/color_convert.h +18 -0
- package/targets/macos/main/color_convert.mm +39 -0
- package/targets/macos/main/font_registry.h +35 -0
- package/targets/macos/main/font_registry.mm +350 -0
- package/targets/macos/main/image_bridge.h +16 -0
- package/targets/macos/main/image_bridge.mm +52 -0
- package/targets/macos/main/macos_app_platform.mm +95 -0
- package/targets/macos/main/macos_apps.c +31 -0
- package/targets/macos/main/macos_device_control.mm +56 -0
- package/targets/macos/main/macos_display.mm +484 -0
- package/targets/macos/main/macos_host_device_control.mm +95 -0
- package/targets/macos/main/macos_main.mm +839 -0
- package/targets/macos/main/macos_memory.cpp +31 -0
- package/targets/macos/main/macos_native_shell.h +31 -0
- package/targets/macos/main/macos_native_shell.mm +396 -0
- package/targets/macos/main/macos_network.mm +253 -0
- package/targets/macos/main/macos_renderer.h +39 -0
- package/targets/macos/main/macos_renderer.mm +1969 -0
- package/targets/macos/main/macos_sensors.cpp +148 -0
- package/targets/macos/main/macos_smoke_app.cpp +65 -0
- package/targets/macos/main/macos_timers.mm +52 -0
- package/targets/macos/main/press_bridge.h +21 -0
- package/targets/macos/main/press_bridge.mm +72 -0
- package/targets/macos/main/thermalright_hid_display.h +14 -0
- package/targets/macos/main/thermalright_hid_display.mm +995 -0
- package/targets/macos/native-artifact-provenance.mjs +421 -0
- package/targets/macos/order-compile-edges.mjs +64 -0
- package/targets/macos/preflight-hard-muted-artifact.mjs +93 -0
- package/targets/macos/prune-generated-modules.mjs +147 -0
- package/targets/macos/sync-resources.mjs +153 -0
- package/targets/shared/analyze-audio-capture.mjs +131 -0
- package/targets/shared/apple_audio.mm +1194 -0
- package/targets/shared/apple_audio_selftest.mm +602 -0
- package/targets/shared/apple_audio_testing.h +34 -0
- package/targets/shared/build-audio-selftest.sh +50 -0
|
@@ -0,0 +1,811 @@
|
|
|
1
|
+
#import <UIKit/UIKit.h>
|
|
2
|
+
#import <QuartzCore/QuartzCore.h>
|
|
3
|
+
|
|
4
|
+
#include "app.h"
|
|
5
|
+
#include "apps.h"
|
|
6
|
+
#include "canvas.h"
|
|
7
|
+
#include "display.h"
|
|
8
|
+
#include "event.h"
|
|
9
|
+
#include "events.h"
|
|
10
|
+
#include "font_registry.h"
|
|
11
|
+
#include "image.h"
|
|
12
|
+
#include "ios_renderer.h"
|
|
13
|
+
#include "resident_apps.h"
|
|
14
|
+
#include "ios_root_background.h"
|
|
15
|
+
#include "pixel.h"
|
|
16
|
+
#include "ui/tree_internal.h"
|
|
17
|
+
|
|
18
|
+
#include <algorithm>
|
|
19
|
+
#include <cmath>
|
|
20
|
+
#include <cstddef>
|
|
21
|
+
#include <cstdint>
|
|
22
|
+
#include <cstdlib>
|
|
23
|
+
#include <cstring>
|
|
24
|
+
|
|
25
|
+
#if __has_include("gea/apple/native_bridge.h")
|
|
26
|
+
#include "gea/apple/native_bridge.h"
|
|
27
|
+
#define GEA_IOS_HAS_APPLE_NATIVE_BRIDGE 1
|
|
28
|
+
#endif
|
|
29
|
+
|
|
30
|
+
extern "C" int gea_embedded_now_ms(void);
|
|
31
|
+
extern "C" void gea_ios_display_set_viewport_size(int width, int height);
|
|
32
|
+
extern "C" const std::uint32_t *gea_ios_display_presented_pixels();
|
|
33
|
+
extern "C" void gea_ios_touch_set_state(int touching, int x, int y);
|
|
34
|
+
|
|
35
|
+
@class GeaAppDelegate;
|
|
36
|
+
static __weak GeaAppDelegate *gGeaAppDelegate = nil;
|
|
37
|
+
|
|
38
|
+
// Wires the iOS AVFoundation camera backend (ios_camera.mm) to the runtime's
|
|
39
|
+
// board-independent <camera> preview seam. Defined in @geastack/host's host/camera.cpp.
|
|
40
|
+
namespace gea::framework::camera {
|
|
41
|
+
void registerCameraSurface();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
namespace {
|
|
45
|
+
|
|
46
|
+
UIColor *rgb565ToUIColor(gea::framework::graphics::pixel::native_t color)
|
|
47
|
+
{
|
|
48
|
+
// Style colours are native pixels (RGBA8888 on iOS) — unpack full 8-bit
|
|
49
|
+
// channels so native UIKit views render true colour, not 565-quantized.
|
|
50
|
+
int r, g, b, a;
|
|
51
|
+
gea::framework::graphics::pixel::unpackNative8(color, &r, &g, &b, &a);
|
|
52
|
+
return [UIColor colorWithRed:static_cast<CGFloat>(r) / 255.0
|
|
53
|
+
green:static_cast<CGFloat>(g) / 255.0
|
|
54
|
+
blue:static_cast<CGFloat>(b) / 255.0
|
|
55
|
+
alpha:1.0];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
NSString *NSStringFromAttr(const char *value)
|
|
59
|
+
{
|
|
60
|
+
return [NSString stringWithUTF8String:value ? value : ""] ?: @"";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
void queueInitialResidentAppFromProcessArguments()
|
|
64
|
+
{
|
|
65
|
+
if (!gea::framework::apps::ResidentApps::isEnabled()) return;
|
|
66
|
+
NSArray<NSString *> *arguments = NSProcessInfo.processInfo.arguments;
|
|
67
|
+
for (NSUInteger i = 0; i + 1 < arguments.count; ++i) {
|
|
68
|
+
if (![arguments[i] isEqualToString:@"--gea-initial-app"]) continue;
|
|
69
|
+
NSString *appId = arguments[i + 1];
|
|
70
|
+
if (appId.length > 0) gea::framework::apps::ResidentApps::requestLaunch(appId.UTF8String);
|
|
71
|
+
}
|
|
72
|
+
const char *envAppId = std::getenv("GEA_IOS_INITIAL_APP");
|
|
73
|
+
if (envAppId && envAppId[0]) gea::framework::apps::ResidentApps::requestLaunch(envAppId);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
NSTextAlignment textAlignmentForStyle(int align)
|
|
77
|
+
{
|
|
78
|
+
switch (align) {
|
|
79
|
+
case 1: return NSTextAlignmentCenter;
|
|
80
|
+
case 2: return NSTextAlignmentRight;
|
|
81
|
+
default: return NSTextAlignmentLeft;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
void addTextDecorationAttributes(NSMutableDictionary *attrs, int textDecoration)
|
|
86
|
+
{
|
|
87
|
+
if (!attrs) return;
|
|
88
|
+
NSNumber *style = @((NSInteger)NSUnderlineStyleSingle);
|
|
89
|
+
if (textDecoration == 1) {
|
|
90
|
+
attrs[NSUnderlineStyleAttributeName] = style;
|
|
91
|
+
} else if (textDecoration == 2) {
|
|
92
|
+
attrs[NSStrikethroughStyleAttributeName] = style;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
NSMutableDictionary *textAttributes(UIFont *font, UIColor *color, int textDecoration)
|
|
97
|
+
{
|
|
98
|
+
NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
|
|
99
|
+
attrs[NSFontAttributeName] = font ?: [UIFont systemFontOfSize:16];
|
|
100
|
+
attrs[NSForegroundColorAttributeName] = color ?: UIColor.blackColor;
|
|
101
|
+
addTextDecorationAttributes(attrs, textDecoration);
|
|
102
|
+
return attrs;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
bool attributedStringHasTextDecoration(NSAttributedString *value)
|
|
106
|
+
{
|
|
107
|
+
if (!value || value.length == 0) return false;
|
|
108
|
+
return [value attribute:NSUnderlineStyleAttributeName atIndex:0 effectiveRange:nil] != nil ||
|
|
109
|
+
[value attribute:NSStrikethroughStyleAttributeName atIndex:0 effectiveRange:nil] != nil;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
} // namespace
|
|
113
|
+
|
|
114
|
+
@interface GeaNativeInputField : UITextField <UITextFieldDelegate>
|
|
115
|
+
@property(nonatomic, assign) int nodeId;
|
|
116
|
+
@property(nonatomic, assign) BOOL syncingFromTree;
|
|
117
|
+
@property(nonatomic, assign) UIEdgeInsets textInsets;
|
|
118
|
+
@end
|
|
119
|
+
|
|
120
|
+
@implementation GeaNativeInputField
|
|
121
|
+
|
|
122
|
+
- (instancetype)initWithFrame:(CGRect)frame
|
|
123
|
+
{
|
|
124
|
+
self = [super initWithFrame:frame];
|
|
125
|
+
if (self) {
|
|
126
|
+
_nodeId = -1;
|
|
127
|
+
_syncingFromTree = NO;
|
|
128
|
+
_textInsets = UIEdgeInsetsZero;
|
|
129
|
+
self.delegate = self;
|
|
130
|
+
self.borderStyle = UITextBorderStyleNone;
|
|
131
|
+
self.backgroundColor = UIColor.clearColor;
|
|
132
|
+
self.opaque = NO;
|
|
133
|
+
self.autocorrectionType = UITextAutocorrectionTypeNo;
|
|
134
|
+
self.autocapitalizationType = UITextAutocapitalizationTypeNone;
|
|
135
|
+
self.spellCheckingType = UITextSpellCheckingTypeNo;
|
|
136
|
+
self.returnKeyType = UIReturnKeyDone;
|
|
137
|
+
if (@available(iOS 11.0, *)) {
|
|
138
|
+
self.smartDashesType = UITextSmartDashesTypeNo;
|
|
139
|
+
self.smartQuotesType = UITextSmartQuotesTypeNo;
|
|
140
|
+
self.smartInsertDeleteType = UITextSmartInsertDeleteTypeNo;
|
|
141
|
+
}
|
|
142
|
+
[self addTarget:self action:@selector(editingChanged:) forControlEvents:UIControlEventEditingChanged];
|
|
143
|
+
}
|
|
144
|
+
return self;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
- (CGRect)textRectForBounds:(CGRect)bounds
|
|
148
|
+
{
|
|
149
|
+
return UIEdgeInsetsInsetRect(bounds, self.textInsets);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
- (CGRect)editingRectForBounds:(CGRect)bounds
|
|
153
|
+
{
|
|
154
|
+
return UIEdgeInsetsInsetRect(bounds, self.textInsets);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
- (CGRect)placeholderRectForBounds:(CGRect)bounds
|
|
158
|
+
{
|
|
159
|
+
return UIEdgeInsetsInsetRect(bounds, self.textInsets);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
- (void)dispatchEventOfType:(gea::framework::events::PointerEventType)type withKeyCode:(int)keyCode
|
|
163
|
+
{
|
|
164
|
+
using gea::framework::events::PointerEvent;
|
|
165
|
+
if (_nodeId < 0) return;
|
|
166
|
+
auto &tree = gea::embedded::ui::Tree::instance();
|
|
167
|
+
if (_nodeId >= tree.nodeCount()) return;
|
|
168
|
+
PointerEvent event;
|
|
169
|
+
event.type = type;
|
|
170
|
+
event.targetId = _nodeId;
|
|
171
|
+
event.keyCode = keyCode;
|
|
172
|
+
tree.dispatchEvent(event);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
- (void)editingChanged:(id)sender
|
|
176
|
+
{
|
|
177
|
+
(void)sender;
|
|
178
|
+
if (_syncingFromTree || _nodeId < 0) return;
|
|
179
|
+
auto &tree = gea::embedded::ui::Tree::instance();
|
|
180
|
+
if (_nodeId >= tree.nodeCount()) return;
|
|
181
|
+
const char *utf8 = self.text ? [self.text UTF8String] : "";
|
|
182
|
+
tree.setAttribute(_nodeId, "value", utf8 ? utf8 : "");
|
|
183
|
+
[self dispatchEventOfType:gea::framework::events::PointerEventType::Input withKeyCode:0];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
- (void)textFieldDidBeginEditing:(UITextField *)textField
|
|
187
|
+
{
|
|
188
|
+
(void)textField;
|
|
189
|
+
if (_nodeId < 0) return;
|
|
190
|
+
gea::embedded::ui::Tree::instance().setActiveInput(_nodeId);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
- (void)textFieldDidEndEditing:(UITextField *)textField
|
|
194
|
+
{
|
|
195
|
+
(void)textField;
|
|
196
|
+
auto &tree = gea::embedded::ui::Tree::instance();
|
|
197
|
+
if (tree.activeInputId() == _nodeId) tree.setActiveInput(-1);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
- (BOOL)textFieldShouldReturn:(UITextField *)textField
|
|
201
|
+
{
|
|
202
|
+
(void)textField;
|
|
203
|
+
[self dispatchEventOfType:gea::framework::events::PointerEventType::KeyDown withKeyCode:13];
|
|
204
|
+
return YES;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
- (void)deleteBackward
|
|
208
|
+
{
|
|
209
|
+
[self dispatchEventOfType:gea::framework::events::PointerEventType::KeyDown withKeyCode:8];
|
|
210
|
+
[super deleteBackward];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
@end
|
|
214
|
+
|
|
215
|
+
@interface GeaDisplayView : UIView
|
|
216
|
+
@property(nonatomic, strong) NSMapTable<UITouch *, NSNumber *> *touchPointerIds;
|
|
217
|
+
- (void)syncNativeTextInputs;
|
|
218
|
+
@end
|
|
219
|
+
|
|
220
|
+
@implementation GeaDisplayView
|
|
221
|
+
|
|
222
|
+
- (instancetype)initWithFrame:(CGRect)frame
|
|
223
|
+
{
|
|
224
|
+
self = [super initWithFrame:frame];
|
|
225
|
+
if (self) {
|
|
226
|
+
self.opaque = YES;
|
|
227
|
+
self.backgroundColor = UIColor.blackColor;
|
|
228
|
+
self.contentMode = UIViewContentModeRedraw;
|
|
229
|
+
self.userInteractionEnabled = YES;
|
|
230
|
+
// Track multiple simultaneous fingers so one button can be held while
|
|
231
|
+
// another is tapped (e.g. move + jump). Keyed by touch identity; the
|
|
232
|
+
// value is the pointer index (0 = primary).
|
|
233
|
+
self.multipleTouchEnabled = YES;
|
|
234
|
+
self.touchPointerIds = [NSMapTable mapTableWithKeyOptions:NSMapTableWeakMemory | NSMapTableObjectPointerPersonality
|
|
235
|
+
valueOptions:NSMapTableStrongMemory];
|
|
236
|
+
}
|
|
237
|
+
return self;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
- (CGFloat)nativeContentScale
|
|
241
|
+
{
|
|
242
|
+
CGFloat scale = self.contentScaleFactor;
|
|
243
|
+
UIScreen *screen = self.window.screen ?: UIScreen.mainScreen;
|
|
244
|
+
if (scale <= 0) scale = screen.nativeScale;
|
|
245
|
+
if (scale <= 0) scale = screen.scale;
|
|
246
|
+
return scale > 0 ? scale : 1;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
- (CGPoint)nativePointForTouch:(UITouch *)touch
|
|
250
|
+
{
|
|
251
|
+
return [self nativePointForViewPoint:[touch locationInView:self]];
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
- (CGPoint)nativePointForViewPoint:(CGPoint)point
|
|
255
|
+
{
|
|
256
|
+
// Map view points into canvas/layout coordinates by the actual view->canvas
|
|
257
|
+
// ratio. The canvas is sized in logical points, so this is ~1; for a
|
|
258
|
+
// physical-pixel canvas it equals the content scale factor. Using the raw
|
|
259
|
+
// content scale factor unconditionally assumed a physical-pixel canvas.
|
|
260
|
+
auto *canvas = gea::platform::display::Display::canvas();
|
|
261
|
+
const CGRect bounds = self.bounds;
|
|
262
|
+
if (canvas && bounds.size.width > 0 && bounds.size.height > 0) {
|
|
263
|
+
point.x *= static_cast<CGFloat>(canvas->width()) / bounds.size.width;
|
|
264
|
+
point.y *= static_cast<CGFloat>(canvas->height()) / bounds.size.height;
|
|
265
|
+
}
|
|
266
|
+
return point;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
- (int)clampedTouchX:(CGFloat)x
|
|
270
|
+
{
|
|
271
|
+
int ix = static_cast<int>(std::lround(x));
|
|
272
|
+
if (auto *canvas = gea::platform::display::Display::canvas()) {
|
|
273
|
+
ix = std::clamp(ix, 0, std::max(0, canvas->width() - 1));
|
|
274
|
+
}
|
|
275
|
+
return std::max(0, ix);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
- (int)clampedTouchY:(CGFloat)y
|
|
279
|
+
{
|
|
280
|
+
int iy = static_cast<int>(std::lround(y));
|
|
281
|
+
if (auto *canvas = gea::platform::display::Display::canvas()) {
|
|
282
|
+
iy = std::clamp(iy, 0, std::max(0, canvas->height() - 1));
|
|
283
|
+
}
|
|
284
|
+
return std::max(0, iy);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
- (CGRect)canvasDestinationRect
|
|
288
|
+
{
|
|
289
|
+
auto *canvas = gea::platform::display::Display::canvas();
|
|
290
|
+
if (!canvas || canvas->width() <= 0 || canvas->height() <= 0) return self.bounds;
|
|
291
|
+
const CGRect bounds = self.bounds;
|
|
292
|
+
const CGFloat scale = std::min(bounds.size.width / static_cast<CGFloat>(canvas->width()),
|
|
293
|
+
bounds.size.height / static_cast<CGFloat>(canvas->height()));
|
|
294
|
+
const CGSize size = CGSizeMake(static_cast<CGFloat>(canvas->width()) * scale,
|
|
295
|
+
static_cast<CGFloat>(canvas->height()) * scale);
|
|
296
|
+
return CGRectMake(bounds.origin.x + (bounds.size.width - size.width) * 0.5,
|
|
297
|
+
bounds.origin.y + (bounds.size.height - size.height) * 0.5,
|
|
298
|
+
size.width,
|
|
299
|
+
size.height);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
- (void)syncNativeTextInputs
|
|
303
|
+
{
|
|
304
|
+
using namespace gea::embedded::ui;
|
|
305
|
+
static NSMutableDictionary<NSNumber *, GeaNativeInputField *> *fields = nil;
|
|
306
|
+
if (!fields) fields = [NSMutableDictionary new];
|
|
307
|
+
|
|
308
|
+
auto *canvas = gea::platform::display::Display::canvas();
|
|
309
|
+
if (!canvas || canvas->width() <= 0 || canvas->height() <= 0) {
|
|
310
|
+
for (GeaNativeInputField *field in [fields allValues]) {
|
|
311
|
+
[field resignFirstResponder];
|
|
312
|
+
[field removeFromSuperview];
|
|
313
|
+
}
|
|
314
|
+
[fields removeAllObjects];
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const CGRect dest = [self canvasDestinationRect];
|
|
319
|
+
const CGFloat canvasScale = dest.size.width / static_cast<CGFloat>(canvas->width());
|
|
320
|
+
auto &tree = Tree::instance();
|
|
321
|
+
const int activeInput = tree.activeInputId();
|
|
322
|
+
NSMutableSet<NSNumber *> *seen = [NSMutableSet set];
|
|
323
|
+
|
|
324
|
+
for (int nodeId = 0; nodeId < tree.nodeCount(); nodeId++) {
|
|
325
|
+
const Node &node = tree.node(nodeId);
|
|
326
|
+
const char *tagName = tree.tagName(nodeId);
|
|
327
|
+
if (node.type != NodeType::View || !tagName || std::strcmp(tagName, "input") != 0) continue;
|
|
328
|
+
|
|
329
|
+
NSNumber *key = @(nodeId);
|
|
330
|
+
[seen addObject:key];
|
|
331
|
+
GeaNativeInputField *field = fields[key];
|
|
332
|
+
if (!field) {
|
|
333
|
+
field = [[GeaNativeInputField alloc] initWithFrame:CGRectZero];
|
|
334
|
+
fields[key] = field;
|
|
335
|
+
[self addSubview:field];
|
|
336
|
+
} else if (field.superview != self) {
|
|
337
|
+
[field removeFromSuperview];
|
|
338
|
+
[self addSubview:field];
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
field.nodeId = nodeId;
|
|
342
|
+
const CGFloat x = dest.origin.x + static_cast<CGFloat>(node.layout.x) * canvasScale;
|
|
343
|
+
const CGFloat y = dest.origin.y + static_cast<CGFloat>(node.layout.y) * canvasScale;
|
|
344
|
+
const CGFloat w = static_cast<CGFloat>(node.layout.width) * canvasScale;
|
|
345
|
+
const CGFloat h = static_cast<CGFloat>(node.layout.height) * canvasScale;
|
|
346
|
+
field.frame = CGRectMake(x, y, w, h);
|
|
347
|
+
field.textInsets = UIEdgeInsetsMake(static_cast<CGFloat>(std::max<int>(0, node.style.padding[0])) * canvasScale,
|
|
348
|
+
static_cast<CGFloat>(std::max<int>(0, node.style.padding[3])) * canvasScale,
|
|
349
|
+
static_cast<CGFloat>(std::max<int>(0, node.style.padding[2])) * canvasScale,
|
|
350
|
+
static_cast<CGFloat>(std::max<int>(0, node.style.padding[1])) * canvasScale);
|
|
351
|
+
field.hidden = (node.style.display == 1 || node.style.opacity == 0 || w <= 0 || h <= 0);
|
|
352
|
+
field.userInteractionEnabled = !field.hidden;
|
|
353
|
+
field.alpha = static_cast<CGFloat>(node.style.opacity) / 255.0;
|
|
354
|
+
field.backgroundColor = node.style.has_bg ? rgb565ToUIColor(node.style.bg_color) : UIColor.clearColor;
|
|
355
|
+
field.layer.borderWidth = static_cast<CGFloat>(std::max<int>(0, node.style.border_width)) * canvasScale;
|
|
356
|
+
field.layer.borderColor = rgb565ToUIColor(node.style.border_color).CGColor;
|
|
357
|
+
const int tl = std::max<int>(0, node.style.border_radius[0]);
|
|
358
|
+
const int tr = std::max<int>(0, node.style.border_radius[1]);
|
|
359
|
+
const int br = std::max<int>(0, node.style.border_radius[2]);
|
|
360
|
+
const int bl = std::max<int>(0, node.style.border_radius[3]);
|
|
361
|
+
const CGFloat radius = (tl == tr && tr == br && br == bl)
|
|
362
|
+
? static_cast<CGFloat>(tl)
|
|
363
|
+
: static_cast<CGFloat>(tl + tr + br + bl) / 4.0;
|
|
364
|
+
field.layer.cornerRadius = radius * canvasScale;
|
|
365
|
+
field.layer.masksToBounds = field.layer.cornerRadius > 0;
|
|
366
|
+
const CGFloat fontSize = std::max<CGFloat>(1.0, static_cast<CGFloat>(node.style.font_size > 0 ? node.style.font_size : 16) * canvasScale);
|
|
367
|
+
field.textColor = rgb565ToUIColor(node.style.text_color);
|
|
368
|
+
field.font = gea::ios::fontForId(node.style.font_id, fontSize);
|
|
369
|
+
field.textAlignment = textAlignmentForStyle(node.style.text_align);
|
|
370
|
+
NSMutableDictionary *textAttrs = textAttributes(field.font, field.textColor, node.style.text_decoration);
|
|
371
|
+
field.defaultTextAttributes = textAttrs;
|
|
372
|
+
|
|
373
|
+
const char *typeAttr = tree.getAttribute(nodeId, "type");
|
|
374
|
+
NSString *type = [NSStringFromAttr(typeAttr) lowercaseString];
|
|
375
|
+
field.secureTextEntry = [type isEqualToString:@"password"];
|
|
376
|
+
if ([type isEqualToString:@"email"]) field.keyboardType = UIKeyboardTypeEmailAddress;
|
|
377
|
+
else if ([type isEqualToString:@"number"]) field.keyboardType = UIKeyboardTypeNumbersAndPunctuation;
|
|
378
|
+
else if ([type isEqualToString:@"tel"]) field.keyboardType = UIKeyboardTypePhonePad;
|
|
379
|
+
else if ([type isEqualToString:@"url"]) field.keyboardType = UIKeyboardTypeURL;
|
|
380
|
+
else field.keyboardType = UIKeyboardTypeDefault;
|
|
381
|
+
|
|
382
|
+
NSString *placeholder = NSStringFromAttr(tree.getAttribute(nodeId, "placeholder"));
|
|
383
|
+
field.placeholder = placeholder.length > 0 ? placeholder : nil;
|
|
384
|
+
|
|
385
|
+
NSString *desiredValue = NSStringFromAttr(tree.getAttribute(nodeId, "value"));
|
|
386
|
+
const BOOL textDiffers = ![(field.text ?: @"") isEqualToString:desiredValue];
|
|
387
|
+
const BOOL usesDecoratedText = node.style.text_decoration != 0;
|
|
388
|
+
if (textDiffers) {
|
|
389
|
+
field.syncingFromTree = YES;
|
|
390
|
+
if (usesDecoratedText) {
|
|
391
|
+
field.attributedText = [[NSAttributedString alloc] initWithString:desiredValue attributes:textAttrs];
|
|
392
|
+
} else {
|
|
393
|
+
field.text = desiredValue;
|
|
394
|
+
}
|
|
395
|
+
field.syncingFromTree = NO;
|
|
396
|
+
} else if (!field.isFirstResponder) {
|
|
397
|
+
if (usesDecoratedText) {
|
|
398
|
+
NSAttributedString *decoratedValue = [[NSAttributedString alloc] initWithString:desiredValue attributes:textAttrs];
|
|
399
|
+
if (![field.attributedText isEqualToAttributedString:decoratedValue]) {
|
|
400
|
+
field.syncingFromTree = YES;
|
|
401
|
+
field.attributedText = decoratedValue;
|
|
402
|
+
field.syncingFromTree = NO;
|
|
403
|
+
}
|
|
404
|
+
} else if (attributedStringHasTextDecoration(field.attributedText)) {
|
|
405
|
+
field.syncingFromTree = YES;
|
|
406
|
+
field.text = desiredValue;
|
|
407
|
+
field.syncingFromTree = NO;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (activeInput == nodeId && !field.hidden) {
|
|
412
|
+
if (!field.isFirstResponder) [field becomeFirstResponder];
|
|
413
|
+
} else if (field.isFirstResponder) {
|
|
414
|
+
[field resignFirstResponder];
|
|
415
|
+
}
|
|
416
|
+
if (!field.hidden) [self bringSubviewToFront:field];
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
for (NSNumber *key in [fields allKeys]) {
|
|
420
|
+
if ([seen containsObject:key]) continue;
|
|
421
|
+
GeaNativeInputField *field = fields[key];
|
|
422
|
+
[field resignFirstResponder];
|
|
423
|
+
[field removeFromSuperview];
|
|
424
|
+
[fields removeObjectForKey:key];
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
- (int)pointerIdForTouch:(UITouch *)touch assignIfAbsent:(BOOL)assign
|
|
429
|
+
{
|
|
430
|
+
NSNumber *existing = [self.touchPointerIds objectForKey:touch];
|
|
431
|
+
if (existing) return existing.intValue;
|
|
432
|
+
if (!assign) return -1;
|
|
433
|
+
// Reuse the lowest free pointer index so ids stay small and stable.
|
|
434
|
+
unsigned int used = 0;
|
|
435
|
+
for (NSNumber *value in [[self.touchPointerIds objectEnumerator] allObjects]) {
|
|
436
|
+
const int v = value.intValue;
|
|
437
|
+
if (v >= 0 && v < 31) used |= (1u << v);
|
|
438
|
+
}
|
|
439
|
+
int pointerId = 0;
|
|
440
|
+
while (pointerId < 31 && (used & (1u << pointerId))) pointerId++;
|
|
441
|
+
[self.touchPointerIds setObject:@(pointerId) forKey:touch];
|
|
442
|
+
return pointerId;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
- (void)dispatchTouchPhase:(gea::framework::events::TouchPhase)phase
|
|
446
|
+
touching:(bool)touching
|
|
447
|
+
nativeX:(int)x
|
|
448
|
+
nativeY:(int)y
|
|
449
|
+
pointerId:(int)pointerId
|
|
450
|
+
{
|
|
451
|
+
// Only the primary pointer (0) drives the single-point hardware touch state
|
|
452
|
+
// that polling apps read; every finger dispatches DOM-style touch events.
|
|
453
|
+
if (pointerId == 0) gea_ios_touch_set_state(touching ? 1 : 0, x, y);
|
|
454
|
+
gea::framework::events::Event event{};
|
|
455
|
+
event.type = gea::framework::events::EventType::Touch;
|
|
456
|
+
event.touchPhase = phase;
|
|
457
|
+
event.touching = touching;
|
|
458
|
+
event.x = x;
|
|
459
|
+
event.y = y;
|
|
460
|
+
event.pointerId = pointerId;
|
|
461
|
+
gea::framework::events::TouchRuntime::dispatchEvent(event);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
- (void)dispatchTouch:(UITouch *)touch
|
|
465
|
+
phase:(gea::framework::events::TouchPhase)phase
|
|
466
|
+
touching:(bool)touching
|
|
467
|
+
{
|
|
468
|
+
if (!touch) return;
|
|
469
|
+
const BOOL isDown = phase == gea::framework::events::TouchPhase::Down;
|
|
470
|
+
const int pointerId = [self pointerIdForTouch:touch assignIfAbsent:isDown];
|
|
471
|
+
if (pointerId < 0) return;
|
|
472
|
+
const CGPoint point = [self nativePointForTouch:touch];
|
|
473
|
+
const int x = [self clampedTouchX:point.x];
|
|
474
|
+
const int y = [self clampedTouchY:point.y];
|
|
475
|
+
[self dispatchTouchPhase:phase touching:touching nativeX:x nativeY:y pointerId:pointerId];
|
|
476
|
+
if (phase == gea::framework::events::TouchPhase::Up) {
|
|
477
|
+
[self.touchPointerIds removeObjectForKey:touch];
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
|
|
482
|
+
{
|
|
483
|
+
(void)event;
|
|
484
|
+
for (UITouch *touch in touches) {
|
|
485
|
+
[self dispatchTouch:touch phase:gea::framework::events::TouchPhase::Down touching:true];
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
|
|
490
|
+
{
|
|
491
|
+
(void)event;
|
|
492
|
+
for (UITouch *touch in touches) {
|
|
493
|
+
[self dispatchTouch:touch phase:gea::framework::events::TouchPhase::Move touching:true];
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
|
|
498
|
+
{
|
|
499
|
+
(void)event;
|
|
500
|
+
for (UITouch *touch in touches) {
|
|
501
|
+
[self dispatchTouch:touch phase:gea::framework::events::TouchPhase::Up touching:false];
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
|
|
506
|
+
{
|
|
507
|
+
(void)event;
|
|
508
|
+
for (UITouch *touch in touches) {
|
|
509
|
+
[self dispatchTouch:touch phase:gea::framework::events::TouchPhase::Up touching:false];
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
- (void)drawRect:(CGRect)dirtyRect
|
|
514
|
+
{
|
|
515
|
+
(void)dirtyRect;
|
|
516
|
+
if (gea::ios::IosRenderer::instance().hasNativeTree()) return;
|
|
517
|
+
auto *canvas = gea::platform::display::Display::canvas();
|
|
518
|
+
if (!canvas) return;
|
|
519
|
+
const std::uint32_t *src = gea_ios_display_presented_pixels();
|
|
520
|
+
const int w = canvas->width();
|
|
521
|
+
const int h = canvas->height();
|
|
522
|
+
if (!src || w <= 0 || h <= 0) return;
|
|
523
|
+
|
|
524
|
+
// The framebuffer is already RGBA8888 (bytes R,G,B,A per pixel) — copy it
|
|
525
|
+
// straight into the CGImage scratch; no RGB565 expansion needed.
|
|
526
|
+
static std::uint8_t *scratch = nullptr;
|
|
527
|
+
static std::size_t scratchBytes = 0;
|
|
528
|
+
const std::size_t bytes = static_cast<std::size_t>(w) * h * 4;
|
|
529
|
+
if (scratchBytes < bytes) {
|
|
530
|
+
void *grown = std::realloc(scratch, bytes);
|
|
531
|
+
if (!grown) return;
|
|
532
|
+
scratch = static_cast<std::uint8_t *>(grown);
|
|
533
|
+
scratchBytes = bytes;
|
|
534
|
+
}
|
|
535
|
+
std::memcpy(scratch, src, bytes);
|
|
536
|
+
|
|
537
|
+
CGColorSpaceRef cs = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
|
|
538
|
+
CGDataProviderRef provider = CGDataProviderCreateWithData(nullptr, scratch, bytes, nullptr);
|
|
539
|
+
const uint32_t bitmapInfo = static_cast<uint32_t>(kCGImageAlphaNoneSkipLast) |
|
|
540
|
+
static_cast<uint32_t>(kCGBitmapByteOrder32Big);
|
|
541
|
+
CGImageRef image = CGImageCreate(static_cast<std::size_t>(w), static_cast<std::size_t>(h),
|
|
542
|
+
8, 32, static_cast<std::size_t>(w) * 4,
|
|
543
|
+
cs, bitmapInfo, provider, nullptr, false,
|
|
544
|
+
kCGRenderingIntentDefault);
|
|
545
|
+
|
|
546
|
+
const CGRect bounds = self.bounds;
|
|
547
|
+
const CGFloat scale = std::min(bounds.size.width / static_cast<CGFloat>(w),
|
|
548
|
+
bounds.size.height / static_cast<CGFloat>(h));
|
|
549
|
+
const CGSize size = CGSizeMake(static_cast<CGFloat>(w) * scale,
|
|
550
|
+
static_cast<CGFloat>(h) * scale);
|
|
551
|
+
const CGRect dest = CGRectMake(bounds.origin.x + (bounds.size.width - size.width) * 0.5,
|
|
552
|
+
bounds.origin.y + (bounds.size.height - size.height) * 0.5,
|
|
553
|
+
size.width,
|
|
554
|
+
size.height);
|
|
555
|
+
UIImage *renderedImage = [UIImage imageWithCGImage:image];
|
|
556
|
+
[renderedImage drawInRect:dest];
|
|
557
|
+
|
|
558
|
+
CGImageRelease(image);
|
|
559
|
+
CGDataProviderRelease(provider);
|
|
560
|
+
CGColorSpaceRelease(cs);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
@end
|
|
564
|
+
|
|
565
|
+
@interface GeaAppDelegate : UIResponder <UIApplicationDelegate>
|
|
566
|
+
@property(nonatomic, strong) UIWindow *window;
|
|
567
|
+
@property(nonatomic, strong) UIView *rootView;
|
|
568
|
+
@property(nonatomic, strong) GeaDisplayView *displayView;
|
|
569
|
+
@property(nonatomic, strong) UIView *nativeRootView;
|
|
570
|
+
@property(nonatomic, strong) UIButton *launcherButton;
|
|
571
|
+
@property(nonatomic, strong) CADisplayLink *displayLink;
|
|
572
|
+
- (void)installNativeRootView:(UIView *)view;
|
|
573
|
+
@end
|
|
574
|
+
|
|
575
|
+
@implementation GeaAppDelegate
|
|
576
|
+
|
|
577
|
+
- (BOOL)shouldShowLauncherButton
|
|
578
|
+
{
|
|
579
|
+
if (!gea::framework::apps::ResidentApps::isEnabled()) return NO;
|
|
580
|
+
const char *activeId = gea::framework::apps::ResidentApps::activeId();
|
|
581
|
+
return activeId && std::strcmp(activeId, gea::framework::apps::AppManager::launcherAppId()) != 0;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
- (void)syncLauncherButton
|
|
585
|
+
{
|
|
586
|
+
if (!self.launcherButton || !self.rootView) return;
|
|
587
|
+
const BOOL show = [self shouldShowLauncherButton];
|
|
588
|
+
UIEdgeInsets safeInsets = self.rootView.safeAreaInsets;
|
|
589
|
+
const CGRect bounds = self.rootView.bounds;
|
|
590
|
+
const CGFloat size = 52.0;
|
|
591
|
+
const CGFloat bottomMargin = 12.0;
|
|
592
|
+
const CGFloat x = (bounds.size.width - size) * 0.5;
|
|
593
|
+
const CGFloat y = bounds.size.height - safeInsets.bottom - bottomMargin - size;
|
|
594
|
+
self.launcherButton.frame = CGRectMake(std::max<CGFloat>(safeInsets.left, x),
|
|
595
|
+
std::max<CGFloat>(safeInsets.top, y),
|
|
596
|
+
size,
|
|
597
|
+
size);
|
|
598
|
+
self.launcherButton.layer.cornerRadius = size * 0.5;
|
|
599
|
+
self.launcherButton.hidden = !show;
|
|
600
|
+
self.launcherButton.userInteractionEnabled = show;
|
|
601
|
+
if (show) [self.rootView bringSubviewToFront:self.launcherButton];
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
- (void)returnToLauncher:(id)sender
|
|
605
|
+
{
|
|
606
|
+
(void)sender;
|
|
607
|
+
if (gea::framework::apps::AppManager::returnRunningAppToLauncher("iOS launcher button")) {
|
|
608
|
+
self.launcherButton.hidden = YES;
|
|
609
|
+
self.launcherButton.userInteractionEnabled = NO;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
- (void)layoutDisplayViewInSafeArea
|
|
614
|
+
{
|
|
615
|
+
if (!self.rootView || !self.displayView) return;
|
|
616
|
+
CGRect frame = self.rootView.safeAreaLayoutGuide.layoutFrame;
|
|
617
|
+
if (frame.size.width <= 0 || frame.size.height <= 0) {
|
|
618
|
+
frame = UIEdgeInsetsInsetRect(self.rootView.bounds, self.rootView.safeAreaInsets);
|
|
619
|
+
}
|
|
620
|
+
if (frame.size.width <= 0 || frame.size.height <= 0) frame = self.rootView.bounds;
|
|
621
|
+
self.displayView.frame = frame;
|
|
622
|
+
if (self.nativeRootView) self.nativeRootView.frame = self.rootView.bounds;
|
|
623
|
+
[self syncLauncherButton];
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
- (void)installNativeRootView:(UIView *)view
|
|
627
|
+
{
|
|
628
|
+
if (!view || !self.rootView) return;
|
|
629
|
+
if (self.nativeRootView && self.nativeRootView.superview == self.rootView) {
|
|
630
|
+
[self.nativeRootView removeFromSuperview];
|
|
631
|
+
}
|
|
632
|
+
self.nativeRootView = view;
|
|
633
|
+
view.frame = self.rootView.bounds;
|
|
634
|
+
view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
635
|
+
view.hidden = NO;
|
|
636
|
+
view.userInteractionEnabled = YES;
|
|
637
|
+
self.displayView.hidden = YES;
|
|
638
|
+
self.displayView.userInteractionEnabled = NO;
|
|
639
|
+
if (self.launcherButton.superview == self.rootView) {
|
|
640
|
+
[self.rootView insertSubview:view belowSubview:self.launcherButton];
|
|
641
|
+
} else {
|
|
642
|
+
[self.rootView addSubview:view];
|
|
643
|
+
}
|
|
644
|
+
[view setNeedsLayout];
|
|
645
|
+
[self syncLauncherButton];
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
- (CGSize)viewportSize
|
|
649
|
+
{
|
|
650
|
+
CGSize size = self.displayView.bounds.size;
|
|
651
|
+
if (size.width <= 0 || size.height <= 0) size = self.rootView.safeAreaLayoutGuide.layoutFrame.size;
|
|
652
|
+
if (size.width <= 0 || size.height <= 0) size = self.rootView.bounds.size;
|
|
653
|
+
if (size.width <= 0 || size.height <= 0) size = self.window.bounds.size;
|
|
654
|
+
if (size.width <= 0 || size.height <= 0) size = UIScreen.mainScreen.bounds.size;
|
|
655
|
+
return size;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
- (CGFloat)viewportScale
|
|
659
|
+
{
|
|
660
|
+
UIScreen *screen = self.displayView.window.screen ?: self.window.screen ?: UIScreen.mainScreen;
|
|
661
|
+
CGFloat scale = screen.nativeScale;
|
|
662
|
+
if (scale <= 0) scale = screen.scale;
|
|
663
|
+
if (scale <= 0) scale = self.displayView.contentScaleFactor;
|
|
664
|
+
return scale > 0 ? scale : 1;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
- (void)syncAppBackgroundColor
|
|
668
|
+
{
|
|
669
|
+
gea::framework::graphics::pixel::native_t rootColor = 0;
|
|
670
|
+
UIColor *color = gea::ios::mountedRootBackgroundColor(&rootColor) ? rgb565ToUIColor(rootColor) : UIColor.blackColor;
|
|
671
|
+
self.window.backgroundColor = color;
|
|
672
|
+
self.rootView.backgroundColor = color;
|
|
673
|
+
self.displayView.backgroundColor = color;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
|
677
|
+
{
|
|
678
|
+
(void)application;
|
|
679
|
+
(void)launchOptions;
|
|
680
|
+
self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
|
|
681
|
+
UIViewController *controller = [[UIViewController alloc] init];
|
|
682
|
+
self.rootView = [[UIView alloc] initWithFrame:self.window.bounds];
|
|
683
|
+
self.rootView.opaque = YES;
|
|
684
|
+
self.rootView.backgroundColor = UIColor.blackColor;
|
|
685
|
+
self.rootView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
686
|
+
self.displayView = [[GeaDisplayView alloc] initWithFrame:CGRectZero];
|
|
687
|
+
self.displayView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
688
|
+
[self.rootView addSubview:self.displayView];
|
|
689
|
+
self.launcherButton = [UIButton buttonWithType:UIButtonTypeSystem];
|
|
690
|
+
self.launcherButton.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.62];
|
|
691
|
+
self.launcherButton.tintColor = UIColor.whiteColor;
|
|
692
|
+
self.launcherButton.layer.borderWidth = 1.0;
|
|
693
|
+
self.launcherButton.layer.borderColor = [UIColor colorWithWhite:1.0 alpha:0.22].CGColor;
|
|
694
|
+
self.launcherButton.clipsToBounds = YES;
|
|
695
|
+
self.launcherButton.hidden = YES;
|
|
696
|
+
self.launcherButton.accessibilityLabel = @"App Launcher";
|
|
697
|
+
if (@available(iOS 13.0, *)) {
|
|
698
|
+
UIImage *image = [UIImage systemImageNamed:@"square.grid.2x2"];
|
|
699
|
+
[self.launcherButton setImage:image forState:UIControlStateNormal];
|
|
700
|
+
} else {
|
|
701
|
+
[self.launcherButton setTitle:@"Apps" forState:UIControlStateNormal];
|
|
702
|
+
self.launcherButton.titleLabel.font = [UIFont boldSystemFontOfSize:13.0];
|
|
703
|
+
}
|
|
704
|
+
[self.launcherButton addTarget:self action:@selector(returnToLauncher:) forControlEvents:UIControlEventTouchUpInside];
|
|
705
|
+
[self.rootView addSubview:self.launcherButton];
|
|
706
|
+
controller.view = self.rootView;
|
|
707
|
+
self.window.rootViewController = controller;
|
|
708
|
+
[self.window makeKeyAndVisible];
|
|
709
|
+
[self.rootView layoutIfNeeded];
|
|
710
|
+
[self layoutDisplayViewInSafeArea];
|
|
711
|
+
gGeaAppDelegate = self;
|
|
712
|
+
|
|
713
|
+
CGSize viewport = [self viewportSize];
|
|
714
|
+
const CGFloat viewportScale = [self viewportScale];
|
|
715
|
+
self.displayView.contentScaleFactor = viewportScale;
|
|
716
|
+
// Viewport/layout space is LOGICAL CSS px (points), NOT physical pixels.
|
|
717
|
+
// Multiplying by viewportScale here made window.innerWidth/innerHeight report
|
|
718
|
+
// physical pixels, so fixed-px app layouts (e.g. Sky Hop's 36px tiles)
|
|
719
|
+
// rendered ~3x too dense on retina. UIKit renders the native view tree
|
|
720
|
+
// crisply at the screen's content scale factor (set above) independently, so
|
|
721
|
+
// gea's internal CSS device-pixel-ratio stays 1 — otherwise `px` lengths get
|
|
722
|
+
// an extra x scale (cssPixelLength) that unitless lengths don't, blowing up
|
|
723
|
+
// fonts (16px/36px) relative to the unitless tile layout.
|
|
724
|
+
const int viewportWidth = std::max(1, static_cast<int>(std::ceil(viewport.width)));
|
|
725
|
+
const int viewportHeight = std::max(1, static_cast<int>(std::ceil(viewport.height)));
|
|
726
|
+
const int devicePixelRatio = 1;
|
|
727
|
+
gea_ios_display_set_viewport_size(viewportWidth, viewportHeight);
|
|
728
|
+
gea::platform::display::Display::init();
|
|
729
|
+
gea::framework::camera::registerCameraSurface();
|
|
730
|
+
// On iOS the build sets GEA_EMBEDDED_PIXEL_FORMAT=GEA_PIXEL_RGBA8888, so the
|
|
731
|
+
// ImageStore decodes straight to full-colour RGBA8888 native pixels (no 565
|
|
732
|
+
// quantization, no separate retained buffer) for native UIImageView.
|
|
733
|
+
gea::framework::app::Application::init(viewportWidth, viewportHeight, devicePixelRatio);
|
|
734
|
+
queueInitialResidentAppFromProcessArguments();
|
|
735
|
+
[self syncAppBackgroundColor];
|
|
736
|
+
[self tick:nil];
|
|
737
|
+
|
|
738
|
+
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(tick:)];
|
|
739
|
+
if (@available(iOS 15.0, *)) {
|
|
740
|
+
self.displayLink.preferredFrameRateRange = CAFrameRateRangeMake(60, 60, 60);
|
|
741
|
+
} else {
|
|
742
|
+
self.displayLink.preferredFramesPerSecond = 60;
|
|
743
|
+
}
|
|
744
|
+
[self.displayLink addToRunLoop:NSRunLoop.mainRunLoop forMode:NSRunLoopCommonModes];
|
|
745
|
+
return YES;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
- (void)tick:(CADisplayLink *)link
|
|
749
|
+
{
|
|
750
|
+
(void)link;
|
|
751
|
+
{
|
|
752
|
+
char pendingId[64] = "";
|
|
753
|
+
if (gea::framework::apps::ResidentApps::consumeLaunch(pendingId, sizeof(pendingId)) &&
|
|
754
|
+
gea::framework::apps::ResidentApps::select(pendingId)) {
|
|
755
|
+
CGSize viewport = [self viewportSize];
|
|
756
|
+
const CGFloat viewportScale = [self viewportScale];
|
|
757
|
+
self.displayView.contentScaleFactor = viewportScale;
|
|
758
|
+
const int viewportWidth = std::max(1, static_cast<int>(std::ceil(viewport.width)));
|
|
759
|
+
const int viewportHeight = std::max(1, static_cast<int>(std::ceil(viewport.height)));
|
|
760
|
+
// gea's internal CSS device-pixel-ratio stays 1 (see init above): the
|
|
761
|
+
// layout is logical points; UIKit handles retina via contentScaleFactor.
|
|
762
|
+
const int devicePixelRatio = 1;
|
|
763
|
+
gea::ios::IosRenderer::instance().teardown();
|
|
764
|
+
gea_ios_display_set_viewport_size(viewportWidth, viewportHeight);
|
|
765
|
+
gea::platform::display::Display::init();
|
|
766
|
+
gea::framework::app::Application::init(viewportWidth, viewportHeight, devicePixelRatio);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
gea::framework::app::Application::frame(gea_embedded_now_ms());
|
|
770
|
+
[self syncAppBackgroundColor];
|
|
771
|
+
[self syncLauncherButton];
|
|
772
|
+
auto &tree = gea::embedded::ui::Tree::instance();
|
|
773
|
+
const int root = tree.mountedRoot();
|
|
774
|
+
if (!self.nativeRootView && root >= 0) {
|
|
775
|
+
gea::ios::IosRenderer::instance().sync(self.displayView, root);
|
|
776
|
+
[self.displayView syncNativeTextInputs];
|
|
777
|
+
[self.displayView setNeedsDisplay];
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
@end
|
|
782
|
+
|
|
783
|
+
#if GEA_IOS_HAS_APPLE_NATIVE_BRIDGE
|
|
784
|
+
void gea::apple::UIKit::installRootView(gea::apple::UIKit::UIView view)
|
|
785
|
+
{
|
|
786
|
+
::UIView *nativeView = (__bridge ::UIView *)gea::apple::objc::object(view.handle);
|
|
787
|
+
if (!nativeView) return;
|
|
788
|
+
if ([NSThread isMainThread]) {
|
|
789
|
+
[gGeaAppDelegate installNativeRootView:nativeView];
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
793
|
+
[gGeaAppDelegate installNativeRootView:nativeView];
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
#endif
|
|
797
|
+
|
|
798
|
+
// The on-screen UIView hosting the gea framebuffer. The camera backend
|
|
799
|
+
// (ios_camera.mm) attaches its AVCaptureVideoPreviewLayer to this view's layer
|
|
800
|
+
// and positions it to a <camera> node's computed rect (NativeOverlay preview).
|
|
801
|
+
extern "C" UIView *gea_ios_camera_host_view(void)
|
|
802
|
+
{
|
|
803
|
+
return gGeaAppDelegate.displayView;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
int main(int argc, char *argv[])
|
|
807
|
+
{
|
|
808
|
+
@autoreleasepool {
|
|
809
|
+
return UIApplicationMain(argc, argv, nil, NSStringFromClass([GeaAppDelegate class]));
|
|
810
|
+
}
|
|
811
|
+
}
|