@lucent-lang/lucent 0.0.4 → 0.1.1
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/README.md +97 -0
- package/app.plugin.js +46 -10
- package/dist/{bench-CR3E7e2e.js → bench-eEez4jhQ.js} +10 -6
- package/dist/{build-CAIrRSYA.js → build-CpUjeHFz.js} +1 -1
- package/dist/{build-check-QozuNJm5.js → build-check-BA1ZEp9s.js} +28 -13
- package/dist/{check-1SCmYlqA.js → check-BZx3FgI6.js} +1 -1
- package/dist/cli.js +62 -13
- package/dist/{codes-CIuwXt1S.js → codes-D-6LxUqD.js} +163 -7
- package/dist/compiler.js +4 -4
- package/dist/{dashboard-B-NzjjaN.js → dashboard-CC-HXhcd.js} +2 -2
- package/dist/{dev-BYWMP7oC.js → dev-DI5yL8zx.js} +62 -23
- package/dist/{diagnostic-BM9sXmwl.js → diagnostic-BvwLZQS1.js} +3 -1
- package/dist/{doctor-C0PrfWT6.js → doctor-CTAX3euc.js} +1 -1
- package/dist/{doctor-DdR4JnVc.js → doctor-CrgOAV5L.js} +1 -1
- package/dist/{explain--vV1oaRl.js → explain-Byit07F-.js} +1 -1
- package/dist/{packages-HyskKIsY.js → packages-CSmKeaAh.js} +2 -33
- package/dist/pipeline-BM_VM2X_.js +700 -0
- package/dist/project-BKNoTEEp.js +342 -0
- package/dist/sdk-coverage-iiv3JqMp.js +113 -0
- package/dist/sdk-diff-ByJ1Gw-F.js +109 -0
- package/dist/sdk-lock-BQn7IyRs.js +38 -0
- package/dist/{sdk-prefetch-mo04JDZV.js → sdk-prefetch-Bv2pNega.js} +2 -2
- package/dist/{sdk-search-Iv4K3-8a.js → sdk-search-CPGJm_Ne.js} +2 -2
- package/dist/{sdk-show-CkrdlqlP.js → sdk-show-BUkLqRnA.js} +5 -4
- package/dist/sdk-usage-0ZFMAYg1.js +95 -0
- package/dist/src-ChnmWLwn.js +40851 -0
- package/dist/trace-Bi4hkmPx.js +139 -0
- package/lib/core.cjs +368 -1
- package/lib/sdk/android.d.ts +54 -1
- package/lib/sdk/compose.d.ts +94 -0
- package/lib/sdk/compose.schemas.json.gz +0 -0
- package/lib/sdk/core.d.ts +201 -0
- package/lib/sdk/ios.d.ts +75 -2
- package/lib/sdk/ui.d.ts +129 -0
- package/metro/transformer.cjs +11 -2
- package/package.json +4 -3
- package/runtime/cpp/lucent/abort.cpp +59 -11
- package/runtime/cpp/lucent/abort.h +22 -6
- package/runtime/cpp/lucent/array.h +12 -2
- package/runtime/cpp/lucent/async.h +101 -21
- package/runtime/cpp/lucent/bigint.cpp +898 -0
- package/runtime/cpp/lucent/bigint.h +302 -0
- package/runtime/cpp/lucent/buffer.cpp +228 -0
- package/runtime/cpp/lucent/buffer.h +328 -0
- package/runtime/cpp/lucent/builtins.cpp +4 -1
- package/runtime/cpp/lucent/bytes.h +21 -2
- package/runtime/cpp/lucent/callback.cpp +29 -0
- package/runtime/cpp/lucent/callback.h +230 -0
- package/runtime/cpp/lucent/compute.cpp +370 -0
- package/runtime/cpp/lucent/compute.h +455 -0
- package/runtime/cpp/lucent/conversions.h +111 -0
- package/runtime/cpp/lucent/core.h +16 -5
- package/runtime/cpp/lucent/equality.h +107 -28
- package/runtime/cpp/lucent/execution.cpp +416 -0
- package/runtime/cpp/lucent/execution.h +270 -0
- package/runtime/cpp/lucent/extension.cpp +97 -0
- package/runtime/cpp/lucent/extension.h +210 -0
- package/runtime/cpp/lucent/helpers.h +12 -5
- package/runtime/cpp/lucent/items.h +52 -0
- package/runtime/cpp/lucent/jserror.cpp +9 -0
- package/runtime/cpp/lucent/jserror.h +11 -0
- package/runtime/cpp/lucent/jsi/convert.cpp +138 -0
- package/runtime/cpp/lucent/jsi/convert.h +128 -59
- package/runtime/cpp/lucent/jsi/host.cpp +213 -35
- package/runtime/cpp/lucent/jsi/host.h +116 -16
- package/runtime/cpp/lucent/json.h +19 -1
- package/runtime/cpp/lucent/lifecycle.cpp +260 -0
- package/runtime/cpp/lucent/lifecycle.h +124 -0
- package/runtime/cpp/lucent/lucent.h +7 -0
- package/runtime/cpp/lucent/map.h +41 -7
- package/runtime/cpp/lucent/native.cpp +35 -47
- package/runtime/cpp/lucent/native.h +116 -26
- package/runtime/cpp/lucent/number.cpp +38 -38
- package/runtime/cpp/lucent/number.h +33 -0
- package/runtime/cpp/lucent/operation.h +65 -0
- package/runtime/cpp/lucent/ops.h +14 -6
- package/runtime/cpp/lucent/platform/android.cpp +223 -10
- package/runtime/cpp/lucent/platform/android.h +227 -4
- package/runtime/cpp/lucent/platform/android_activity.cpp +258 -0
- package/runtime/cpp/lucent/platform/android_requests.h +140 -0
- package/runtime/cpp/lucent/platform/compose.h +256 -0
- package/runtime/cpp/lucent/platform/ios.h +325 -25
- package/runtime/cpp/lucent/platform/ios.mm +1 -14
- package/runtime/cpp/lucent/platform/ios_ui.h +192 -0
- package/runtime/cpp/lucent/platform/ios_ui.mm +454 -0
- package/runtime/cpp/lucent/platform/swiftui.h +190 -0
- package/runtime/cpp/lucent/presentation.cpp +11 -0
- package/runtime/cpp/lucent/presentation.h +109 -0
- package/runtime/cpp/lucent/reactive.cpp +406 -0
- package/runtime/cpp/lucent/reactive.h +566 -0
- package/runtime/cpp/lucent/report.cpp +40 -0
- package/runtime/cpp/lucent/report.h +16 -0
- package/runtime/cpp/lucent/resource.cpp +83 -0
- package/runtime/cpp/lucent/resource.h +75 -0
- package/runtime/cpp/lucent/scheduler.cpp +38 -124
- package/runtime/cpp/lucent/scheduler.h +102 -83
- package/runtime/cpp/lucent/scope.cpp +308 -0
- package/runtime/cpp/lucent/scope.h +452 -0
- package/runtime/cpp/lucent/sizing.h +263 -0
- package/runtime/cpp/lucent/slots.h +231 -0
- package/runtime/cpp/lucent/trace.cpp +539 -0
- package/runtime/cpp/lucent/trace.h +251 -0
- package/runtime/cpp/lucent/transport.cpp +9 -0
- package/runtime/cpp/lucent/transport.h +341 -0
- package/runtime/cpp/lucent/view.cpp +123 -0
- package/runtime/cpp/lucent/view.h +157 -0
- package/runtime/cpp/rn/LucentComponentView.h +121 -0
- package/runtime/cpp/rn/LucentComponentView.mm +479 -0
- package/runtime/cpp/rn/LucentModule.cpp +37 -7
- package/runtime/cpp/rn/LucentModule.h +3 -0
- package/runtime/cpp/rn/LucentViewChildren.h +80 -0
- package/runtime/cpp/rn/LucentViewChildren.mm +211 -0
- package/runtime/cpp/rn/LucentViewRegistry.h +62 -0
- package/runtime/cpp/rn/LucentViewRequests.cpp +91 -0
- package/runtime/cpp/rn/LucentViewRequests.h +104 -0
- package/runtime/cpp/rn/LucentViewSizing.h +345 -0
- package/runtime/cpp/rn/LucentViewSlots.h +242 -0
- package/runtime/cpp/rn/LucentViewValues.h +125 -0
- package/runtime/cpp/rn/LucentViews.h +440 -0
- package/runtime/cpp/rn/LucentViewsAndroid.cpp +439 -0
- package/runtime/cpp/rn/LucentViewsAndroid.h +138 -0
- package/runtime/cpp/third_party/dragonbox/LICENSE-Boost +23 -0
- package/runtime/cpp/third_party/dragonbox/README.md +16 -0
- package/runtime/cpp/third_party/dragonbox/dragonbox.h +4440 -0
- package/runtime/js/index.d.ts +11 -1
- package/runtime/js/index.js +84 -8
- package/runtime/js/views.d.ts +38 -0
- package/runtime/js/views.js +226 -0
- package/runtime/native/android/CMakeLists.txt +4 -1
- package/runtime/native/android/src/compose/java/dev/lucent/compose/CompositionRules.kt +44 -0
- package/runtime/native/android/src/compose/java/dev/lucent/compose/LucentComposition.kt +161 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentActivities.java +292 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentChildren.java +174 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentHostView.java +232 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentInitializer.java +46 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentPackage.java +4 -3
- package/runtime/native/android/src/main/java/dev/lucent/LucentRequestActivity.java +92 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentSlotView.java +102 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentViewManager.java +149 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentViewManagers.java +16 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentViews.java +44 -0
- package/runtime/test/jsi/harness.cpp +9 -0
- package/schemas/build.schema.json +96 -1
- package/schemas/check.schema.json +56 -1
- package/schemas/lucent.schema.json +495 -9
- package/schemas/sdk-diff.schema.json +70 -0
- package/schemas/sdk-lock.schema.json +67 -0
- package/ts-plugin/index.js +85 -4
- package/dist/pipeline-CH7-2cxu.js +0 -232
- package/dist/project-D8tEet0k.js +0 -150
- package/dist/sdk-coverage-BoW_3Efd.js +0 -64
- package/dist/src-BD7f1_aZ.js +0 -11493
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
// Lucent runtime — UIKit glue: UIKit's lifecycle notifications into the
|
|
2
|
+
// shared Lifecycle, the presentation context, and showing view controllers.
|
|
3
|
+
#include "ios_ui.h"
|
|
4
|
+
|
|
5
|
+
#import <objc/runtime.h>
|
|
6
|
+
|
|
7
|
+
#include <array>
|
|
8
|
+
#include <string>
|
|
9
|
+
|
|
10
|
+
using namespace lucent;
|
|
11
|
+
|
|
12
|
+
namespace {
|
|
13
|
+
|
|
14
|
+
// --- the notifications, as data -----------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
struct AppNotification {
|
|
17
|
+
const char* name;
|
|
18
|
+
AppEvent event;
|
|
19
|
+
NSNotificationName __unsafe_unretained notification;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
struct SceneNotification {
|
|
23
|
+
const char* name;
|
|
24
|
+
SceneEvent event;
|
|
25
|
+
NSNotificationName __unsafe_unretained notification;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const std::array<AppNotification, 6>& appNotifications() {
|
|
29
|
+
static const std::array<AppNotification, 6> table = {{
|
|
30
|
+
{"didBecomeActive", AppEvent::DidBecomeActive, UIApplicationDidBecomeActiveNotification},
|
|
31
|
+
{"willResignActive", AppEvent::WillResignActive, UIApplicationWillResignActiveNotification},
|
|
32
|
+
{"didEnterBackground", AppEvent::DidEnterBackground, UIApplicationDidEnterBackgroundNotification},
|
|
33
|
+
{"willEnterForeground", AppEvent::WillEnterForeground, UIApplicationWillEnterForegroundNotification},
|
|
34
|
+
{"didReceiveMemoryWarning", AppEvent::DidReceiveMemoryWarning, UIApplicationDidReceiveMemoryWarningNotification},
|
|
35
|
+
{"willTerminate", AppEvent::WillTerminate, UIApplicationWillTerminateNotification},
|
|
36
|
+
}};
|
|
37
|
+
return table;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const std::array<SceneNotification, 6>& sceneNotifications() {
|
|
41
|
+
static const std::array<SceneNotification, 6> table = {{
|
|
42
|
+
{"willConnect", SceneEvent::WillConnect, UISceneWillConnectNotification},
|
|
43
|
+
{"didDisconnect", SceneEvent::DidDisconnect, UISceneDidDisconnectNotification},
|
|
44
|
+
{"didActivate", SceneEvent::DidActivate, UISceneDidActivateNotification},
|
|
45
|
+
{"willDeactivate", SceneEvent::WillDeactivate, UISceneWillDeactivateNotification},
|
|
46
|
+
{"willEnterForeground", SceneEvent::WillEnterForeground, UISceneWillEnterForegroundNotification},
|
|
47
|
+
{"didEnterBackground", SceneEvent::DidEnterBackground, UISceneDidEnterBackgroundNotification},
|
|
48
|
+
}};
|
|
49
|
+
return table;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// The entry of `table` named `name`; TypeError for any other name.
|
|
53
|
+
template <class Table>
|
|
54
|
+
const auto& named(const Table& table, const String& name, const char* what) {
|
|
55
|
+
std::string wanted = name.toUtf8();
|
|
56
|
+
for (const auto& entry : table)
|
|
57
|
+
if (wanted == entry.name) return entry;
|
|
58
|
+
|
|
59
|
+
throw Exception(makeError(String::fromLatin1("TypeError"), String::fromUtf8(std::string("Unknown ") + what + ": " + wanted)));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
SceneState stateOf(UIScene* scene) {
|
|
63
|
+
// Only window scenes can present.
|
|
64
|
+
if (![scene isKindOfClass:[UIWindowScene class]]) return SceneState::Unattached;
|
|
65
|
+
|
|
66
|
+
switch (scene.activationState) {
|
|
67
|
+
case UISceneActivationStateForegroundActive:
|
|
68
|
+
return SceneState::ForegroundActive;
|
|
69
|
+
case UISceneActivationStateForegroundInactive:
|
|
70
|
+
return SceneState::ForegroundInactive;
|
|
71
|
+
case UISceneActivationStateBackground:
|
|
72
|
+
return SceneState::Background;
|
|
73
|
+
default:
|
|
74
|
+
return SceneState::Unattached;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
void requireMain(const char* what) {
|
|
79
|
+
if (onMainThread()) return;
|
|
80
|
+
|
|
81
|
+
throwError(String::fromLatin1("InvalidStateError"), String::fromUtf8(std::string(what) + " is only available on the main thread"));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
} // namespace
|
|
85
|
+
|
|
86
|
+
// --- scenes and their ids -------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
/// Tags a scene with its SceneId. It goes with the scene: if UIKit never
|
|
89
|
+
/// said the scene disconnected, its going says so.
|
|
90
|
+
@interface LucentSceneTag : NSObject
|
|
91
|
+
@property(nonatomic, readonly) SceneId sceneId;
|
|
92
|
+
- (instancetype)initWithSceneId:(SceneId)sceneId;
|
|
93
|
+
@end
|
|
94
|
+
|
|
95
|
+
@implementation LucentSceneTag
|
|
96
|
+
|
|
97
|
+
- (instancetype)initWithSceneId:(SceneId)sceneId {
|
|
98
|
+
if ((self = [super init])) _sceneId = sceneId;
|
|
99
|
+
return self;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
- (void)dealloc {
|
|
103
|
+
SceneId id = _sceneId;
|
|
104
|
+
lucent::detail::runOn(&ExecutionContext::main(), [id] {
|
|
105
|
+
if (Lifecycle::shared().sceneScope(id)) Lifecycle::shared().report(id, SceneEvent::DidDisconnect);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@end
|
|
110
|
+
|
|
111
|
+
namespace {
|
|
112
|
+
|
|
113
|
+
char tagKey;
|
|
114
|
+
|
|
115
|
+
/// Connected scenes by id, held weakly. Main thread.
|
|
116
|
+
NSMapTable<NSNumber*, UIScene*>* scenesById() {
|
|
117
|
+
static NSMapTable<NSNumber*, UIScene*>* table = [NSMapTable strongToWeakObjectsMapTable];
|
|
118
|
+
return table;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
SceneId taggedId(UIScene* scene) {
|
|
122
|
+
LucentSceneTag* tag = objc_getAssociatedObject(scene, &tagKey);
|
|
123
|
+
return tag ? tag.sceneId : 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/// The scene's id, connecting it first if it has none.
|
|
127
|
+
SceneId tag(UIScene* scene) {
|
|
128
|
+
if (SceneId known = taggedId(scene)) return known;
|
|
129
|
+
|
|
130
|
+
SceneId id = Lifecycle::shared().connect();
|
|
131
|
+
objc_setAssociatedObject(scene, &tagKey, [[LucentSceneTag alloc] initWithSceneId:id], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
132
|
+
[scenesById() setObject:scene forKey:@(id)];
|
|
133
|
+
|
|
134
|
+
return id;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/// Scenes connected before Lucent heard of them.
|
|
138
|
+
void adoptConnectedScenes() {
|
|
139
|
+
UIApplication* app = [UIApplication sharedApplication];
|
|
140
|
+
if (!app) return;
|
|
141
|
+
|
|
142
|
+
for (UIScene* scene in app.connectedScenes) tag(scene);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
void sceneNotified(UIScene* scene, SceneEvent event) {
|
|
146
|
+
if (![scene isKindOfClass:[UIScene class]]) return;
|
|
147
|
+
|
|
148
|
+
SceneId id = tag(scene);
|
|
149
|
+
Lifecycle::shared().report(id, event);
|
|
150
|
+
|
|
151
|
+
if (event != SceneEvent::DidDisconnect) return;
|
|
152
|
+
|
|
153
|
+
// Should UIKit connect this object again, it is a new scene.
|
|
154
|
+
[scenesById() removeObjectForKey:@(id)];
|
|
155
|
+
objc_setAssociatedObject(scene, &tagKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
UIWindow* keyWindowOf(UIWindowScene* scene) {
|
|
159
|
+
if (scene.keyWindow) return scene.keyWindow;
|
|
160
|
+
|
|
161
|
+
// Not key yet (still activating): its first visible window at the normal level.
|
|
162
|
+
for (UIWindow* window in scene.windows)
|
|
163
|
+
if (!window.hidden && window.windowLevel == UIWindowLevelNormal) return window;
|
|
164
|
+
|
|
165
|
+
return nil;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
UIViewController* topOf(UIViewController* root) {
|
|
169
|
+
UIViewController* top = root;
|
|
170
|
+
|
|
171
|
+
while (top.presentedViewController && !top.presentedViewController.isBeingDismissed) top = top.presentedViewController;
|
|
172
|
+
|
|
173
|
+
return top;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
std::optional<objc::PresentationContext> contextOf(SceneId id) {
|
|
177
|
+
auto* scene = (UIWindowScene*)[scenesById() objectForKey:@(id)];
|
|
178
|
+
if (![scene isKindOfClass:[UIWindowScene class]]) return std::nullopt;
|
|
179
|
+
|
|
180
|
+
UIWindow* window = keyWindowOf(scene);
|
|
181
|
+
UIViewController* top = topOf(window.rootViewController);
|
|
182
|
+
if (!top) return std::nullopt;
|
|
183
|
+
|
|
184
|
+
return objc::PresentationContext{id, scene, window, top};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
} // namespace
|
|
188
|
+
|
|
189
|
+
/// Follows UIKit's lifecycle notifications from the moment the binary loads,
|
|
190
|
+
/// before any scene connects, on the main thread (where UIKit posts them;
|
|
191
|
+
/// one posted elsewhere is passed there). The observers stay for the
|
|
192
|
+
/// process.
|
|
193
|
+
@interface LucentLifecycleObserver : NSObject
|
|
194
|
+
@end
|
|
195
|
+
|
|
196
|
+
@implementation LucentLifecycleObserver
|
|
197
|
+
|
|
198
|
+
+ (void)load {
|
|
199
|
+
NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
|
|
200
|
+
|
|
201
|
+
for (const auto& n : sceneNotifications()) {
|
|
202
|
+
SceneEvent event = n.event;
|
|
203
|
+
[center addObserverForName:n.notification
|
|
204
|
+
object:nil
|
|
205
|
+
queue:nil
|
|
206
|
+
usingBlock:^(NSNotification* note) {
|
|
207
|
+
id scene = note.object;
|
|
208
|
+
lucent::detail::runOn(&ExecutionContext::main(), [scene, event] { sceneNotified(scene, event); });
|
|
209
|
+
}];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
for (const auto& n : appNotifications()) {
|
|
213
|
+
AppEvent event = n.event;
|
|
214
|
+
[center addObserverForName:n.notification
|
|
215
|
+
object:nil
|
|
216
|
+
queue:nil
|
|
217
|
+
usingBlock:^(NSNotification*) {
|
|
218
|
+
lucent::detail::runOn(&ExecutionContext::main(), [event] { Lifecycle::shared().report(event); });
|
|
219
|
+
}];
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
@end
|
|
224
|
+
|
|
225
|
+
// --- presenting ----------------------------------------------------------------------------
|
|
226
|
+
|
|
227
|
+
/// Watches a presented view controller for the person dismissing it (as its
|
|
228
|
+
/// presentation controller's delegate, if it had none) and for its release
|
|
229
|
+
/// (associated with it), until disarmed.
|
|
230
|
+
@interface LucentPresentationWatch : NSObject <UIAdaptivePresentationControllerDelegate>
|
|
231
|
+
- (instancetype)initWithDismissed:(std::function<void()>)dismissed;
|
|
232
|
+
- (void)disarm;
|
|
233
|
+
@end
|
|
234
|
+
|
|
235
|
+
@implementation LucentPresentationWatch {
|
|
236
|
+
std::function<void()> _dismissed;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
- (instancetype)initWithDismissed:(std::function<void()>)dismissed {
|
|
240
|
+
if ((self = [super init])) _dismissed = std::move(dismissed);
|
|
241
|
+
return self;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
- (void)fire {
|
|
245
|
+
std::function<void()> dismissed = std::move(_dismissed);
|
|
246
|
+
_dismissed = nullptr;
|
|
247
|
+
if (dismissed) dismissed();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
- (void)disarm {
|
|
251
|
+
_dismissed = nullptr;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
- (void)presentationControllerDidDismiss:(UIPresentationController*)presentationController {
|
|
255
|
+
// Settling may unwatch, releasing this object: keep it until it returns.
|
|
256
|
+
LucentPresentationWatch* watch = self;
|
|
257
|
+
[watch fire];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
- (void)dealloc {
|
|
261
|
+
[self fire];
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
@end
|
|
265
|
+
|
|
266
|
+
namespace {
|
|
267
|
+
|
|
268
|
+
char watchKey;
|
|
269
|
+
|
|
270
|
+
/// A popover needs an anchor (UIActivityViewController on iPad): the middle
|
|
271
|
+
/// of the presenting view, unless the view controller has its own.
|
|
272
|
+
void anchorPopover(UIViewController* viewController, UIViewController* from) {
|
|
273
|
+
if (viewController.modalPresentationStyle != UIModalPresentationPopover) return;
|
|
274
|
+
|
|
275
|
+
UIPopoverPresentationController* popover = viewController.popoverPresentationController;
|
|
276
|
+
if (!popover || popover.sourceView || popover.barButtonItem) return;
|
|
277
|
+
if (@available(iOS 16.0, *)) {
|
|
278
|
+
if (popover.sourceItem) return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
CGRect bounds = from.view.bounds;
|
|
282
|
+
popover.sourceView = from.view;
|
|
283
|
+
popover.sourceRect = CGRectMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds), 0, 0);
|
|
284
|
+
popover.permittedArrowDirections = 0;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
void unwatch(UIViewController* viewController, LucentPresentationWatch* watch) {
|
|
288
|
+
[watch disarm];
|
|
289
|
+
|
|
290
|
+
if (viewController.presentationController.delegate == watch) viewController.presentationController.delegate = nil;
|
|
291
|
+
if (objc_getAssociatedObject(viewController, &watchKey) == watch)
|
|
292
|
+
objc_setAssociatedObject(viewController, &watchKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
[[noreturn]] void refuse(const std::string& why) {
|
|
296
|
+
throwError(String::fromLatin1("InvalidStateError"), String::fromUtf8(why));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
} // namespace
|
|
300
|
+
|
|
301
|
+
namespace lucent::objc {
|
|
302
|
+
|
|
303
|
+
std::optional<PresentationContext> presentationContext() {
|
|
304
|
+
requireMain("The presentation context");
|
|
305
|
+
adoptConnectedScenes();
|
|
306
|
+
|
|
307
|
+
std::optional<SceneId> picked = presentingScene(Lifecycle::shared().scenes(), [](SceneId id) {
|
|
308
|
+
return stateOf([scenesById() objectForKey:@(id)]);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
return picked ? contextOf(*picked) : std::nullopt;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
SceneId sceneIdOf(UIScene* scene) {
|
|
315
|
+
requireMain("A scene's id");
|
|
316
|
+
return scene ? tag(scene) : 0;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
UIScene* sceneOf(SceneId id) {
|
|
320
|
+
requireMain("A scene");
|
|
321
|
+
return [scenesById() objectForKey:@(id)];
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
std::function<void()> detail::show(SceneId scene, UIViewController* viewController, bool animated, std::function<void()> dismissed) {
|
|
325
|
+
requireMain("Presenting");
|
|
326
|
+
|
|
327
|
+
std::optional<PresentationContext> context = contextOf(scene);
|
|
328
|
+
if (!context) refuse("The scene has no window to present from");
|
|
329
|
+
|
|
330
|
+
if (viewController.presentingViewController || viewController.isBeingPresented || viewController.parentViewController ||
|
|
331
|
+
viewController.viewIfLoaded.window)
|
|
332
|
+
refuse("The view controller is already shown");
|
|
333
|
+
|
|
334
|
+
// UIKit logs, rather than throws, when it refuses to present during
|
|
335
|
+
// another presentation or dismissal: refuse first.
|
|
336
|
+
UIViewController* top = context->top;
|
|
337
|
+
if (top.presentedViewController || top.isBeingPresented || top.isBeingDismissed) refuse("Another presentation is under way");
|
|
338
|
+
|
|
339
|
+
anchorPopover(viewController, top);
|
|
340
|
+
|
|
341
|
+
LucentPresentationWatch* watch = [[LucentPresentationWatch alloc] initWithDismissed:std::move(dismissed)];
|
|
342
|
+
objc_setAssociatedObject(viewController, &watchKey, watch, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
343
|
+
|
|
344
|
+
UIPresentationController* controller = viewController.presentationController;
|
|
345
|
+
if (controller && !controller.delegate) controller.delegate = watch;
|
|
346
|
+
|
|
347
|
+
// Some view controllers (UIActivityViewController) are shown later than
|
|
348
|
+
// the call, once their content is ready: a dismissal asked for before
|
|
349
|
+
// then waits for it. Main thread only.
|
|
350
|
+
struct Presenting {
|
|
351
|
+
bool shown = false;
|
|
352
|
+
bool withdrawn = false;
|
|
353
|
+
};
|
|
354
|
+
auto presenting = std::make_shared<Presenting>();
|
|
355
|
+
|
|
356
|
+
__weak UIViewController* weakViewController = viewController;
|
|
357
|
+
void (^dismiss)(void) = ^{
|
|
358
|
+
UIViewController* shown = weakViewController;
|
|
359
|
+
UIViewController* presenter = shown.presentingViewController;
|
|
360
|
+
if (presenter && !shown.isBeingDismissed) [presenter dismissViewControllerAnimated:animated completion:nil];
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
NSString* failure = nil;
|
|
364
|
+
@try {
|
|
365
|
+
[top presentViewController:viewController
|
|
366
|
+
animated:animated
|
|
367
|
+
completion:^{
|
|
368
|
+
presenting->shown = true;
|
|
369
|
+
if (presenting->withdrawn) dismiss();
|
|
370
|
+
}];
|
|
371
|
+
} @catch (NSException* e) {
|
|
372
|
+
failure = e.reason ?: e.name;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (failure) {
|
|
376
|
+
unwatch(viewController, watch);
|
|
377
|
+
refuse(failure.UTF8String);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
__weak LucentPresentationWatch* weakWatch = watch;
|
|
381
|
+
return [weakViewController, weakWatch, presenting, dismiss] {
|
|
382
|
+
presenting->withdrawn = true;
|
|
383
|
+
|
|
384
|
+
UIViewController* shown = weakViewController;
|
|
385
|
+
LucentPresentationWatch* watching = weakWatch;
|
|
386
|
+
if (shown && watching) unwatch(shown, watching);
|
|
387
|
+
|
|
388
|
+
if (presenting->shown) dismiss();
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
Error detail::dismissedError() {
|
|
393
|
+
return makeError(String::fromLatin1("AbortError"), String::fromLatin1("The view controller was dismissed"));
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
std::shared_ptr<Scope> detail::callerScope() { return ExecutionContext::of(ExecutionContext::currentRef()).root(); }
|
|
397
|
+
|
|
398
|
+
namespace {
|
|
399
|
+
|
|
400
|
+
/// A subscription as the function that ends it, also ended by `signal`.
|
|
401
|
+
Fn<void()> stopper(std::shared_ptr<Resource> subscription, Opt<AbortSignal> signal) {
|
|
402
|
+
AbortSignal abort = signal.has() ? signal.get() : nullptr;
|
|
403
|
+
uint64_t listener = 0;
|
|
404
|
+
|
|
405
|
+
if (abort) {
|
|
406
|
+
std::weak_ptr<Resource> weak = subscription;
|
|
407
|
+
listener = abort->add([weak] {
|
|
408
|
+
if (auto s = weak.lock()) s->close();
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
std::weak_ptr<AbortSignalObject> weakSignal = abort;
|
|
413
|
+
return Fn<void()>([subscription, weakSignal, listener] {
|
|
414
|
+
subscription->close();
|
|
415
|
+
if (auto s = weakSignal.lock(); s && listener) s->remove(listener);
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
bool abortedAlready(const Opt<AbortSignal>& signal) { return signal.has() && signal.get() && signal.get()->aborted.load(); }
|
|
420
|
+
|
|
421
|
+
/// The session's persistent identifier of a connected scene, else "".
|
|
422
|
+
String identifierOf(SceneId id) {
|
|
423
|
+
NSString* identifier = [scenesById() objectForKey:@(id)].session.persistentIdentifier;
|
|
424
|
+
return identifier ? fromNSString(identifier, "persistentIdentifier") : String();
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
} // namespace
|
|
428
|
+
|
|
429
|
+
Fn<void()> onAppEvent(const String& event, Fn<void()> listener, Opt<AbortSignal> signal) {
|
|
430
|
+
AppEvent e = named(appNotifications(), event, "app event").event;
|
|
431
|
+
if (abortedAlready(signal)) return Fn<void()>([] {});
|
|
432
|
+
|
|
433
|
+
auto subscription = Lifecycle::shared().subscribe(
|
|
434
|
+
e, [listener] { callNow([&] { listener(); }); }, detail::callerScope());
|
|
435
|
+
|
|
436
|
+
return stopper(std::move(subscription), signal);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
Fn<void()> onSceneEvent(const String& event, Fn<void(String)> listener, Opt<AbortSignal> signal) {
|
|
440
|
+
SceneEvent e = named(sceneNotifications(), event, "scene event").event;
|
|
441
|
+
if (abortedAlready(signal)) return Fn<void()>([] {});
|
|
442
|
+
|
|
443
|
+
auto subscription = Lifecycle::shared().subscribe(
|
|
444
|
+
e,
|
|
445
|
+
[listener](SceneId scene) {
|
|
446
|
+
String identifier = identifierOf(scene);
|
|
447
|
+
callNow([&] { listener(identifier); });
|
|
448
|
+
},
|
|
449
|
+
detail::callerScope());
|
|
450
|
+
|
|
451
|
+
return stopper(std::move(subscription), signal);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
} // namespace lucent::objc
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// Lucent runtime — what the glue of SwiftUI components shares (iOS,
|
|
2
|
+
// Objective-C++ with ARC).
|
|
3
|
+
//
|
|
4
|
+
// A SwiftUI component's body is Swift, generated from the JSX its setup
|
|
5
|
+
// returns; the rest of the setup is Lucent code. The
|
|
6
|
+
// generated Swift calls back into it through C functions: the body's
|
|
7
|
+
// actions (the setup's functions a tap… calls) by index, with the
|
|
8
|
+
// arguments the Swift gives them, the Lucent code `withAnimation`
|
|
9
|
+
// animates, and a change of the body's size. All run on the main thread;
|
|
10
|
+
// actions and animations in Lucent's main context.
|
|
11
|
+
#pragma once
|
|
12
|
+
|
|
13
|
+
#import <Foundation/Foundation.h>
|
|
14
|
+
|
|
15
|
+
#include <cstddef>
|
|
16
|
+
#include <cstdint>
|
|
17
|
+
#include <exception>
|
|
18
|
+
#include <functional>
|
|
19
|
+
#include <initializer_list>
|
|
20
|
+
#include <memory>
|
|
21
|
+
#include <type_traits>
|
|
22
|
+
#include <utility>
|
|
23
|
+
#include <vector>
|
|
24
|
+
|
|
25
|
+
#include "../array.h"
|
|
26
|
+
#include "../function.h"
|
|
27
|
+
#include "../items.h"
|
|
28
|
+
#include "../native.h"
|
|
29
|
+
#include "../view.h"
|
|
30
|
+
#include "ios.h"
|
|
31
|
+
|
|
32
|
+
namespace lucent::swiftui {
|
|
33
|
+
|
|
34
|
+
/// How the Swift side runs action `action` of `context`, with `args`: a
|
|
35
|
+
/// retained NSArray of its arguments, which the call takes.
|
|
36
|
+
using Invoke = void (*)(void* context, int32_t action, void* args);
|
|
37
|
+
/// How it lets `context` go, when its view goes.
|
|
38
|
+
using Release = void (*)(void* context);
|
|
39
|
+
/// How it reports that the body's size changed.
|
|
40
|
+
using Resized = void (*)(void* context);
|
|
41
|
+
/// The Lucent code withAnimation runs.
|
|
42
|
+
using Body = void (*)(void* context);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The actions a body calls, by index, and the content of their mount. The
|
|
46
|
+
* Swift side holds them through a context (context()) until its view
|
|
47
|
+
* goes; the mount clears them when it ends, so what they captured goes
|
|
48
|
+
* with it.
|
|
49
|
+
*/
|
|
50
|
+
struct Actions {
|
|
51
|
+
explicit Actions(std::weak_ptr<ui::Content> content) : content(std::move(content)) {}
|
|
52
|
+
|
|
53
|
+
std::vector<std::function<void(NSArray* args)>> list;
|
|
54
|
+
/// The mount's content, which a change of the body's size marks.
|
|
55
|
+
std::weak_ptr<ui::Content> content;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/// A context the Swift side owns (release() lets it go), holding `actions`.
|
|
59
|
+
inline void* context(const std::shared_ptr<Actions>& actions) {
|
|
60
|
+
return new std::shared_ptr<Actions>(actions);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// Runs an action, which Swift calls on the main thread, in the main context.
|
|
64
|
+
inline void invoke(void* context, int32_t action, void* args) {
|
|
65
|
+
NSArray* given = (__bridge_transfer NSArray*)args;
|
|
66
|
+
auto actions = *static_cast<std::shared_ptr<Actions>*>(context);
|
|
67
|
+
|
|
68
|
+
if (action < 0 || static_cast<std::size_t>(action) >= actions->list.size()) return;
|
|
69
|
+
|
|
70
|
+
// A copy: the action may end the mount, which clears the list.
|
|
71
|
+
auto run = actions->list[static_cast<std::size_t>(action)];
|
|
72
|
+
|
|
73
|
+
if (run) callNowIn(ExecutionContext::main(), [&] { run(given); });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
namespace detail {
|
|
77
|
+
|
|
78
|
+
/// An action's argument `i`, as the Lucent function takes it; missing, or NSNull, is none.
|
|
79
|
+
template <class T>
|
|
80
|
+
struct Arg;
|
|
81
|
+
|
|
82
|
+
template <>
|
|
83
|
+
struct Arg<double> {
|
|
84
|
+
static double from(id v) { return [(NSNumber*)v doubleValue]; }
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
template <>
|
|
88
|
+
struct Arg<bool> {
|
|
89
|
+
static bool from(id v) { return [(NSNumber*)v boolValue]; }
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
template <>
|
|
93
|
+
struct Arg<String> {
|
|
94
|
+
static String from(id v) { return objc::fromNSString((NSString*)v, "a SwiftUI argument"); }
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
template <class T>
|
|
98
|
+
struct Arg<Opt<T>> {
|
|
99
|
+
static Opt<T> from(id v) { return v && v != [NSNull null] ? Opt<T>(Arg<T>::from(v)) : Opt<T>(null); }
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
template <class T>
|
|
103
|
+
T arg(NSArray* args, std::size_t i) {
|
|
104
|
+
return Arg<T>::from(i < args.count ? args[i] : nil);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
template <class... A, std::size_t... I>
|
|
108
|
+
void call(const Fn<void(A...)>& f, NSArray* args, std::index_sequence<I...>) {
|
|
109
|
+
f(arg<A>(args, I)...);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
} // namespace detail
|
|
113
|
+
|
|
114
|
+
/// The setup function `f` as an action: the Swift side's arguments, in order, are its parameters.
|
|
115
|
+
template <class... A>
|
|
116
|
+
std::function<void(NSArray*)> action(Fn<void(A...)> f) {
|
|
117
|
+
return [f = std::move(f)](NSArray* args) { detail::call(f, args, std::index_sequence_for<A...>{}); };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
inline void release(void* context) { delete static_cast<std::shared_ptr<Actions>*>(context); }
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* The body's size changed, SwiftUI's own layout too (no Lucent code ran):
|
|
124
|
+
* its mount's content is marked, so the host measures it again on a later
|
|
125
|
+
* turn of the main thread. Swift calls it on the main thread.
|
|
126
|
+
*/
|
|
127
|
+
inline void resized(void* context) {
|
|
128
|
+
ui::invalidateSize((*static_cast<std::shared_ptr<Actions>*>(context))->content);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// --- plain data, encoded for the Swift side (emit/toolkit-values.ts) ----------------------
|
|
132
|
+
|
|
133
|
+
inline id value(double v) { return @(v); }
|
|
134
|
+
inline id value(bool v) { return @(v); }
|
|
135
|
+
inline id value(const String& v) { return objc::toNSString(v); }
|
|
136
|
+
|
|
137
|
+
/// NSNull when `v` is null or undefined, else `each` of its value.
|
|
138
|
+
template <class T, class F>
|
|
139
|
+
id optional(const Opt<T>& v, F each) {
|
|
140
|
+
return v.has() ? each(v.get()) : [NSNull null];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
template <class T, class F>
|
|
144
|
+
id array(const Array<T>& a, F each) {
|
|
145
|
+
NSMutableArray* out = [NSMutableArray arrayWithCapacity:a.size()];
|
|
146
|
+
|
|
147
|
+
for (std::size_t i = 0; i < a.size(); i++) [out addObject:each(a.at(i))];
|
|
148
|
+
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/// An object's fields, in order.
|
|
153
|
+
inline id record(std::initializer_list<id> fields) {
|
|
154
|
+
return [NSArray arrayWithObjects:fields.begin() count:fields.size()];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/// A retained object the Swift side returns (a hosting controller), as Lucent's reference.
|
|
158
|
+
inline NativeRef adopt(void* retained, const char* what) {
|
|
159
|
+
return objc::wrap((__bridge_transfer id)retained, what);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* withAnimation: `shim` runs SwiftUI's withAnimation around a call of
|
|
164
|
+
* `body`, now, with the animation's Lucent values (`args`, as its C
|
|
165
|
+
* types). Its writes are one transaction, whose effects (the values the
|
|
166
|
+
* body shows) run before withAnimation returns, so SwiftUI animates what
|
|
167
|
+
* they change. What `body` throws is thrown here, once Swift is done.
|
|
168
|
+
*/
|
|
169
|
+
template <class F, class... A>
|
|
170
|
+
void animate(void (*shim)(void* context, Body body, A...), F body, std::type_identity_t<A>... args) {
|
|
171
|
+
struct Call {
|
|
172
|
+
F* body;
|
|
173
|
+
std::exception_ptr error;
|
|
174
|
+
} call{&body, nullptr};
|
|
175
|
+
|
|
176
|
+
shim(&call, [](void* context) {
|
|
177
|
+
auto* c = static_cast<Call*>(context);
|
|
178
|
+
|
|
179
|
+
// Nothing may unwind through Swift's frames.
|
|
180
|
+
try {
|
|
181
|
+
ui::mainGraph()->transaction([&] { (*c->body)(); });
|
|
182
|
+
} catch (...) {
|
|
183
|
+
c->error = std::current_exception();
|
|
184
|
+
}
|
|
185
|
+
}, args...);
|
|
186
|
+
|
|
187
|
+
if (call.error) std::rethrow_exception(call.error);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
} // namespace lucent::swiftui
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#include "presentation.h"
|
|
2
|
+
|
|
3
|
+
namespace lucent {
|
|
4
|
+
|
|
5
|
+
Error sceneGoneError() { return makeError(String::fromLatin1("AbortError"), String::fromLatin1("The scene disconnected")); }
|
|
6
|
+
|
|
7
|
+
Error noSceneError() {
|
|
8
|
+
return makeError(String::fromLatin1("InvalidStateError"), String::fromLatin1("No scene is in the foreground to present from"));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
} // namespace lucent
|