basalt-core 0.0.0-rc5

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.
Files changed (325) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cli/basalt.d.ts +3 -0
  3. package/dist/cli/basalt.d.ts.map +1 -0
  4. package/dist/cli/basalt.js +47 -0
  5. package/dist/cli/basalt.js.map +1 -0
  6. package/dist/cli/bundle-cli.d.ts +12 -0
  7. package/dist/cli/bundle-cli.d.ts.map +1 -0
  8. package/dist/cli/bundle-cli.js +124 -0
  9. package/dist/cli/bundle-cli.js.map +1 -0
  10. package/dist/cli/bundleWithAssets.d.ts +79 -0
  11. package/dist/cli/bundleWithAssets.d.ts.map +1 -0
  12. package/dist/cli/bundleWithAssets.js +198 -0
  13. package/dist/cli/bundleWithAssets.js.map +1 -0
  14. package/dist/cli/desktop.d.ts +339 -0
  15. package/dist/cli/desktop.d.ts.map +1 -0
  16. package/dist/cli/desktop.js +1029 -0
  17. package/dist/cli/desktop.js.map +1 -0
  18. package/dist/cli/doctor.d.ts +31 -0
  19. package/dist/cli/doctor.d.ts.map +1 -0
  20. package/dist/cli/doctor.js +283 -0
  21. package/dist/cli/doctor.js.map +1 -0
  22. package/dist/cli/init.d.ts +77 -0
  23. package/dist/cli/init.d.ts.map +1 -0
  24. package/dist/cli/init.js +442 -0
  25. package/dist/cli/init.js.map +1 -0
  26. package/dist/cli/metro.d.ts +49 -0
  27. package/dist/cli/metro.d.ts.map +1 -0
  28. package/dist/cli/metro.js +148 -0
  29. package/dist/cli/metro.js.map +1 -0
  30. package/dist/cli/packageApp.d.ts +102 -0
  31. package/dist/cli/packageApp.d.ts.map +1 -0
  32. package/dist/cli/packageApp.js +319 -0
  33. package/dist/cli/packageApp.js.map +1 -0
  34. package/dist/cli/steps.d.ts +34 -0
  35. package/dist/cli/steps.d.ts.map +1 -0
  36. package/dist/cli/steps.js +53 -0
  37. package/dist/cli/steps.js.map +1 -0
  38. package/dist/metro-config.d.ts +168 -0
  39. package/dist/metro-config.d.ts.map +1 -0
  40. package/dist/metro-config.js +671 -0
  41. package/dist/metro-config.js.map +1 -0
  42. package/dist/react-native.config.d.ts +7 -0
  43. package/dist/react-native.config.d.ts.map +1 -0
  44. package/dist/react-native.config.js +80 -0
  45. package/dist/react-native.config.js.map +1 -0
  46. package/dist/src/DragSource.d.ts +52 -0
  47. package/dist/src/DragSource.d.ts.map +1 -0
  48. package/dist/src/DragSource.js +97 -0
  49. package/dist/src/DragSource.js.map +1 -0
  50. package/dist/src/DropTarget.d.ts +58 -0
  51. package/dist/src/DropTarget.d.ts.map +1 -0
  52. package/dist/src/DropTarget.js +131 -0
  53. package/dist/src/DropTarget.js.map +1 -0
  54. package/dist/src/KeyHandler.d.ts +57 -0
  55. package/dist/src/KeyHandler.d.ts.map +1 -0
  56. package/dist/src/KeyHandler.js +48 -0
  57. package/dist/src/KeyHandler.js.map +1 -0
  58. package/dist/src/Menu.d.ts +72 -0
  59. package/dist/src/Menu.d.ts.map +1 -0
  60. package/dist/src/Menu.js +179 -0
  61. package/dist/src/Menu.js.map +1 -0
  62. package/dist/src/TitleBar.d.ts +75 -0
  63. package/dist/src/TitleBar.d.ts.map +1 -0
  64. package/dist/src/TitleBar.js +236 -0
  65. package/dist/src/TitleBar.js.map +1 -0
  66. package/dist/src/Window.d.ts +87 -0
  67. package/dist/src/Window.d.ts.map +1 -0
  68. package/dist/src/Window.js +248 -0
  69. package/dist/src/Window.js.map +1 -0
  70. package/dist/src/closeRequest.d.ts +55 -0
  71. package/dist/src/closeRequest.d.ts.map +1 -0
  72. package/dist/src/closeRequest.js +134 -0
  73. package/dist/src/closeRequest.js.map +1 -0
  74. package/dist/src/index.d.ts +45 -0
  75. package/dist/src/index.d.ts.map +1 -0
  76. package/dist/src/index.js +92 -0
  77. package/dist/src/index.js.map +1 -0
  78. package/dist/src/overrides/Alert.d.ts +69 -0
  79. package/dist/src/overrides/Alert.d.ts.map +1 -0
  80. package/dist/src/overrides/Alert.js +112 -0
  81. package/dist/src/overrides/Alert.js.map +1 -0
  82. package/dist/src/overrides/BaseViewConfig.d.ts +28 -0
  83. package/dist/src/overrides/BaseViewConfig.d.ts.map +1 -0
  84. package/dist/src/overrides/BaseViewConfig.js +72 -0
  85. package/dist/src/overrides/BaseViewConfig.js.map +1 -0
  86. package/dist/src/overrides/Platform.linux.d.ts +10 -0
  87. package/dist/src/overrides/Platform.linux.d.ts.map +1 -0
  88. package/dist/src/overrides/Platform.linux.js +15 -0
  89. package/dist/src/overrides/Platform.linux.js.map +1 -0
  90. package/dist/src/overrides/Platform.macos.d.ts +10 -0
  91. package/dist/src/overrides/Platform.macos.d.ts.map +1 -0
  92. package/dist/src/overrides/Platform.macos.js +15 -0
  93. package/dist/src/overrides/Platform.macos.js.map +1 -0
  94. package/dist/src/overrides/Platform.windows.d.ts +10 -0
  95. package/dist/src/overrides/Platform.windows.d.ts.map +1 -0
  96. package/dist/src/overrides/Platform.windows.js +15 -0
  97. package/dist/src/overrides/Platform.windows.js.map +1 -0
  98. package/dist/src/overrides/ReactDevToolsSettingsManager.d.ts +24 -0
  99. package/dist/src/overrides/ReactDevToolsSettingsManager.d.ts.map +1 -0
  100. package/dist/src/overrides/ReactDevToolsSettingsManager.js +30 -0
  101. package/dist/src/overrides/ReactDevToolsSettingsManager.js.map +1 -0
  102. package/dist/src/overrides/RefreshControl.d.ts +52 -0
  103. package/dist/src/overrides/RefreshControl.d.ts.map +1 -0
  104. package/dist/src/overrides/RefreshControl.js +92 -0
  105. package/dist/src/overrides/RefreshControl.js.map +1 -0
  106. package/dist/src/overrides/ScrollView.d.ts +39 -0
  107. package/dist/src/overrides/ScrollView.d.ts.map +1 -0
  108. package/dist/src/overrides/ScrollView.js +59 -0
  109. package/dist/src/overrides/ScrollView.js.map +1 -0
  110. package/dist/src/overrides/Share.d.ts +96 -0
  111. package/dist/src/overrides/Share.d.ts.map +1 -0
  112. package/dist/src/overrides/Share.js +97 -0
  113. package/dist/src/overrides/Share.js.map +1 -0
  114. package/dist/src/overrides/TextInput.d.ts +81 -0
  115. package/dist/src/overrides/TextInput.d.ts.map +1 -0
  116. package/dist/src/overrides/TextInput.js +213 -0
  117. package/dist/src/overrides/TextInput.js.map +1 -0
  118. package/dist/src/overrides/View.d.ts +36 -0
  119. package/dist/src/overrides/View.d.ts.map +1 -0
  120. package/dist/src/overrides/View.js +99 -0
  121. package/dist/src/overrides/View.js.map +1 -0
  122. package/dist/src/overrides/createPlatform.d.ts +32 -0
  123. package/dist/src/overrides/createPlatform.d.ts.map +1 -0
  124. package/dist/src/overrides/createPlatform.js +84 -0
  125. package/dist/src/overrides/createPlatform.js.map +1 -0
  126. package/dist/src/overrides/setUpDeveloperTools.d.ts +28 -0
  127. package/dist/src/overrides/setUpDeveloperTools.d.ts.map +1 -0
  128. package/dist/src/overrides/setUpDeveloperTools.js +84 -0
  129. package/dist/src/overrides/setUpDeveloperTools.js.map +1 -0
  130. package/dist/src/overrides/setUpXHR.d.ts +46 -0
  131. package/dist/src/overrides/setUpXHR.d.ts.map +1 -0
  132. package/dist/src/overrides/setUpXHR.js +236 -0
  133. package/dist/src/overrides/setUpXHR.js.map +1 -0
  134. package/dist/src/quitRequest.d.ts +55 -0
  135. package/dist/src/quitRequest.d.ts.map +1 -0
  136. package/dist/src/quitRequest.js +121 -0
  137. package/dist/src/quitRequest.js.map +1 -0
  138. package/dist/src/resourcePath.d.ts +18 -0
  139. package/dist/src/resourcePath.d.ts.map +1 -0
  140. package/dist/src/resourcePath.js +31 -0
  141. package/dist/src/resourcePath.js.map +1 -0
  142. package/dist/src/responseBody.d.ts +76 -0
  143. package/dist/src/responseBody.d.ts.map +1 -0
  144. package/dist/src/responseBody.js +94 -0
  145. package/dist/src/responseBody.js.map +1 -0
  146. package/dist/src/responseHeaders.d.ts +40 -0
  147. package/dist/src/responseHeaders.d.ts.map +1 -0
  148. package/dist/src/responseHeaders.js +75 -0
  149. package/dist/src/responseHeaders.js.map +1 -0
  150. package/dist/src/titleBarState.d.ts +43 -0
  151. package/dist/src/titleBarState.d.ts.map +1 -0
  152. package/dist/src/titleBarState.js +77 -0
  153. package/dist/src/titleBarState.js.map +1 -0
  154. package/dist/src/useContextMenu.d.ts +113 -0
  155. package/dist/src/useContextMenu.d.ts.map +1 -0
  156. package/dist/src/useContextMenu.js +109 -0
  157. package/dist/src/useContextMenu.js.map +1 -0
  158. package/dist/src/useDialog.d.ts +98 -0
  159. package/dist/src/useDialog.d.ts.map +1 -0
  160. package/dist/src/useDialog.js +112 -0
  161. package/dist/src/useDialog.js.map +1 -0
  162. package/dist/src/useDisplays.d.ts +70 -0
  163. package/dist/src/useDisplays.d.ts.map +1 -0
  164. package/dist/src/useDisplays.js +118 -0
  165. package/dist/src/useDisplays.js.map +1 -0
  166. package/dist/src/useHandledKeys.d.ts +38 -0
  167. package/dist/src/useHandledKeys.d.ts.map +1 -0
  168. package/dist/src/useHandledKeys.js +70 -0
  169. package/dist/src/useHandledKeys.js.map +1 -0
  170. package/dist/src/useWindow.d.ts +140 -0
  171. package/dist/src/useWindow.d.ts.map +1 -0
  172. package/dist/src/useWindow.js +233 -0
  173. package/dist/src/useWindow.js.map +1 -0
  174. package/dist/tsconfig.tsbuildinfo +1 -0
  175. package/native/CMakeLists.txt +355 -0
  176. package/native/bootstrap.sh +613 -0
  177. package/native/cmake/CompilerFlags.cmake +49 -0
  178. package/native/cmake/Expo.cmake +125 -0
  179. package/native/cmake/ReactNativeCore.cmake +612 -0
  180. package/native/cmake/Reanimated.cmake +108 -0
  181. package/native/cmake/Skia.cmake +379 -0
  182. package/native/cmake/ThirdParty.cmake +290 -0
  183. package/native/cmake/Worklets.cmake +67 -0
  184. package/native/cmake/codegen/CMakeLists.txt +32 -0
  185. package/native/core/AppIdentity.cpp +85 -0
  186. package/native/core/AppIdentity.h +59 -0
  187. package/native/core/AppPaths.cpp +96 -0
  188. package/native/core/AppPaths.h +53 -0
  189. package/native/core/AppearanceModule.cpp +60 -0
  190. package/native/core/AppearanceModule.h +40 -0
  191. package/native/core/Backface.h +59 -0
  192. package/native/core/BlobModule.cpp +283 -0
  193. package/native/core/BlobModule.h +76 -0
  194. package/native/core/BlobRegistry.cpp +75 -0
  195. package/native/core/BlobRegistry.h +44 -0
  196. package/native/core/ColorScheme.cpp +105 -0
  197. package/native/core/ColorScheme.h +59 -0
  198. package/native/core/ComponentRegistry.h +30 -0
  199. package/native/core/ControlMetrics.h +37 -0
  200. package/native/core/CoreModules.cpp +592 -0
  201. package/native/core/CoreModules.h +308 -0
  202. package/native/core/DebuggingOverlay.cpp +106 -0
  203. package/native/core/DebuggingOverlay.h +65 -0
  204. package/native/core/DesktopControls.cpp +232 -0
  205. package/native/core/DesktopControls.h +170 -0
  206. package/native/core/DevBundle.cpp +108 -0
  207. package/native/core/DevBundle.h +69 -0
  208. package/native/core/DevMenu.cpp +99 -0
  209. package/native/core/DevMenu.h +75 -0
  210. package/native/core/DialogModule.cpp +150 -0
  211. package/native/core/DialogModule.h +51 -0
  212. package/native/core/DragAndDrop.cpp +45 -0
  213. package/native/core/DragAndDrop.h +225 -0
  214. package/native/core/ExpoImageComponent.cpp +79 -0
  215. package/native/core/ExpoImageComponent.h +80 -0
  216. package/native/core/ExpoModules.cpp +415 -0
  217. package/native/core/ExpoModules.h +115 -0
  218. package/native/core/ExpoRuntime.cpp +366 -0
  219. package/native/core/ExpoRuntime.h +42 -0
  220. package/native/core/FocusRing.h +36 -0
  221. package/native/core/FontRegistry.h +51 -0
  222. package/native/core/GestureHandlerModule.cpp +136 -0
  223. package/native/core/GestureHandlerModule.h +71 -0
  224. package/native/core/Gestures.cpp +533 -0
  225. package/native/core/Gestures.h +145 -0
  226. package/native/core/HoverTracker.cpp +69 -0
  227. package/native/core/HoverTracker.h +109 -0
  228. package/native/core/HttpClient.cpp +282 -0
  229. package/native/core/ImageBytes.cpp +205 -0
  230. package/native/core/ImageBytes.h +29 -0
  231. package/native/core/ImageCache.cpp +51 -0
  232. package/native/core/ImageCache.h +89 -0
  233. package/native/core/JsRuntimeAccess.cpp +70 -0
  234. package/native/core/JsRuntimeAccess.h +60 -0
  235. package/native/core/JsiPromise.h +51 -0
  236. package/native/core/KeyEvents.cpp +116 -0
  237. package/native/core/KeyEvents.h +144 -0
  238. package/native/core/LogBoxSurface.cpp +46 -0
  239. package/native/core/LogBoxSurface.h +68 -0
  240. package/native/core/MenuModel.h +117 -0
  241. package/native/core/MenuModule.cpp +209 -0
  242. package/native/core/MenuModule.h +66 -0
  243. package/native/core/MountingWalk.h +524 -0
  244. package/native/core/PackageModules.cpp.in +25 -0
  245. package/native/core/PackageModules.h +50 -0
  246. package/native/core/PlatformConstantsModule.cpp +63 -0
  247. package/native/core/PlatformConstantsModule.h +64 -0
  248. package/native/core/PlatformServices.h +213 -0
  249. package/native/core/PointerButtons.h +63 -0
  250. package/native/core/PullToRefresh.cpp +57 -0
  251. package/native/core/PullToRefresh.h +73 -0
  252. package/native/core/ReanimatedCompat.h +47 -0
  253. package/native/core/ReanimatedModule.cpp +203 -0
  254. package/native/core/ReanimatedModule.h +55 -0
  255. package/native/core/ScrollAnimation.cpp +59 -0
  256. package/native/core/ScrollAnimation.h +70 -0
  257. package/native/core/ScrollBounds.cpp +26 -0
  258. package/native/core/ScrollBounds.h +68 -0
  259. package/native/core/ScrollIndicator.cpp +50 -0
  260. package/native/core/ScrollIndicator.h +69 -0
  261. package/native/core/ScrollMomentum.cpp +71 -0
  262. package/native/core/ScrollMomentum.h +73 -0
  263. package/native/core/ScrollSnap.cpp +123 -0
  264. package/native/core/ScrollSnap.h +80 -0
  265. package/native/core/ShareFallback.cpp +101 -0
  266. package/native/core/ShareFallback.h +45 -0
  267. package/native/core/SkiaPictureViewComponent.cpp +11 -0
  268. package/native/core/SkiaPictureViewComponent.h +47 -0
  269. package/native/core/SourceCodeModule.cpp +55 -0
  270. package/native/core/SourceCodeModule.h +53 -0
  271. package/native/core/StatusBarModule.cpp +23 -0
  272. package/native/core/StatusBarModule.h +42 -0
  273. package/native/core/TestDialog.cpp +148 -0
  274. package/native/core/TestDialog.h +96 -0
  275. package/native/core/TestKeys.cpp +78 -0
  276. package/native/core/TestKeys.h +50 -0
  277. package/native/core/TestQuitFile.cpp +33 -0
  278. package/native/core/TestQuitFile.h +60 -0
  279. package/native/core/TestSettle.h +192 -0
  280. package/native/core/TitleBarRegions.h +31 -0
  281. package/native/core/TurboModuleProxy.cpp +51 -0
  282. package/native/core/TurboModuleProxy.h +46 -0
  283. package/native/core/UIManagerAccess.cpp +43 -0
  284. package/native/core/UIManagerAccess.h +43 -0
  285. package/native/core/WindowBoundsCache.cpp +319 -0
  286. package/native/core/WindowControl.h +235 -0
  287. package/native/core/WindowHost.h +158 -0
  288. package/native/core/WindowMethods.cpp +167 -0
  289. package/native/core/WindowMethods.h +84 -0
  290. package/native/core/WindowsModule.cpp +448 -0
  291. package/native/core/WindowsModule.h +133 -0
  292. package/native/core/WorkletsModule.cpp +199 -0
  293. package/native/core/WorkletsModule.h +57 -0
  294. package/native/core/portability_probe.cpp +210 -0
  295. package/native/tests/EventRecorder.h +124 -0
  296. package/native/tests/TestHarness.cpp +109 -0
  297. package/native/tests/TestHarness.h +89 -0
  298. package/native/tests/TreeDump.h +49 -0
  299. package/native/tests/test_app_identity.cpp +114 -0
  300. package/native/tests/test_app_paths.cpp +48 -0
  301. package/native/tests/test_appearance.cpp +107 -0
  302. package/native/tests/test_backface.cpp +79 -0
  303. package/native/tests/test_blobs.cpp +157 -0
  304. package/native/tests/test_controls.cpp +185 -0
  305. package/native/tests/test_devbundle.cpp +103 -0
  306. package/native/tests/test_drag_and_drop.cpp +143 -0
  307. package/native/tests/test_gestures.cpp +331 -0
  308. package/native/tests/test_hover.cpp +92 -0
  309. package/native/tests/test_image_cache.cpp +109 -0
  310. package/native/tests/test_js_runtime_access.cpp +137 -0
  311. package/native/tests/test_key_events.cpp +235 -0
  312. package/native/tests/test_momentum.cpp +138 -0
  313. package/native/tests/test_quit_file.cpp +104 -0
  314. package/native/tests/test_scroll_animation.cpp +120 -0
  315. package/native/tests/test_scroll_bounds.cpp +66 -0
  316. package/native/tests/test_scroll_indicator.cpp +149 -0
  317. package/native/tests/test_scroll_snap.cpp +112 -0
  318. package/native/tests/test_settle.cpp +115 -0
  319. package/native/tests/test_share.cpp +75 -0
  320. package/native/tests/test_sourcecode.cpp +62 -0
  321. package/native/tests/test_window_close.cpp +119 -0
  322. package/native/tests/test_window_limits.cpp +123 -0
  323. package/package.json +43 -0
  324. package/react-native.config.js +86 -0
  325. package/supported-versions.json +66 -0
@@ -0,0 +1,366 @@
1
+ #include "ExpoRuntime.h"
2
+
3
+ #include "ExpoModules.h"
4
+ #include "FontRegistry.h"
5
+
6
+ #include <filesystem>
7
+ #include <random>
8
+ #include <string>
9
+ #include <string_view>
10
+
11
+ #ifdef BASALT_HAS_EXPO
12
+ #include <EventEmitter.h>
13
+ #include <JSI/JSIUtils.h>
14
+ #include <NativeModule.h>
15
+ #include <SharedObject.h>
16
+ #include <SharedRef.h>
17
+ #endif
18
+
19
+ namespace basalt {
20
+
21
+ bool hasExpoRuntime() {
22
+ #ifdef BASALT_HAS_EXPO
23
+ return true;
24
+ #else
25
+ return false;
26
+ #endif
27
+ }
28
+
29
+ #ifdef BASALT_HAS_EXPO
30
+
31
+ namespace {
32
+
33
+ // A version 4 UUID: 122 random bits, with the version and variant fields set as
34
+ // RFC 4122 requires. `std::random_device` rather than a seeded generator --
35
+ // this names things that may outlive the process and must not repeat between
36
+ // two runs started in the same millisecond.
37
+ std::string randomUuidV4() {
38
+ std::random_device entropy;
39
+ std::uniform_int_distribution<unsigned> nibble(0, 15);
40
+
41
+ static constexpr char kHex[] = "0123456789abcdef";
42
+ std::string uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";
43
+ for (char &c : uuid) {
44
+ if (c == 'x') {
45
+ c = kHex[nibble(entropy)];
46
+ } else if (c == 'y') {
47
+ // The variant: one of 8, 9, a or b.
48
+ c = kHex[(nibble(entropy) & 0x3U) | 0x8U];
49
+ }
50
+ }
51
+ return uuid;
52
+ }
53
+
54
+ using facebook::jsi::Function;
55
+ using facebook::jsi::Object;
56
+ using facebook::jsi::PropNameID;
57
+ using facebook::jsi::Runtime;
58
+ using facebook::jsi::String;
59
+ using facebook::jsi::Value;
60
+
61
+ // An already-settled promise, which is all this module needs: registering a
62
+ // font is synchronous, and the JavaScript signature is async only because it is
63
+ // asynchronous everywhere else.
64
+ Value resolvedPromise(Runtime &rt) {
65
+ return rt.global()
66
+ .getPropertyAsObject(rt, "Promise")
67
+ .getPropertyAsFunction(rt, "resolve")
68
+ .call(rt);
69
+ }
70
+
71
+ Value rejectedPromise(Runtime &rt, const std::string &message) {
72
+ Function error = rt.global().getPropertyAsFunction(rt, "Error");
73
+ Value reason = error.callAsConstructor(rt, String::createFromUtf8(rt, message));
74
+ return rt.global()
75
+ .getPropertyAsObject(rt, "Promise")
76
+ .getPropertyAsFunction(rt, "reject")
77
+ .call(rt, reason);
78
+ }
79
+
80
+ // expo-font hands over whatever `expo-asset` resolved: a string, or an object
81
+ // with a localUri or uri. Both shapes appear depending on how the asset was
82
+ // bundled, so both are read rather than one being assumed.
83
+ std::string localPathFrom(Runtime &rt, const Value &resource) {
84
+ std::string uri;
85
+ if (resource.isString()) {
86
+ uri = resource.asString(rt).utf8(rt);
87
+ } else if (resource.isObject()) {
88
+ Object object = resource.asObject(rt);
89
+ for (const char *key : {"localUri", "uri"}) {
90
+ Value value = object.getProperty(rt, key);
91
+ if (value.isString()) {
92
+ uri = value.asString(rt).utf8(rt);
93
+ break;
94
+ }
95
+ }
96
+ }
97
+
98
+ if (uri.rfind("file://", 0) == 0) {
99
+ uri.erase(0, std::string_view{"file://"}.size());
100
+ }
101
+ return uri;
102
+ }
103
+
104
+ // ExpoAsset, which is how expo-asset turns an asset's URL into a local file.
105
+ //
106
+ // For a `file://` URL there is nothing to download: React Native resolved it
107
+ // beside the bundle and it is already on disk, so the work is confirming that
108
+ // and handing the same URL back. The existence check is the point -- without it
109
+ // a missing asset succeeds here and fails much later, somewhere that has no
110
+ // idea which file was meant.
111
+ //
112
+ // An http URL is what a dev server produces, and fetching one is not
113
+ // implemented. It rejects saying so rather than pretending.
114
+ Object makeAssetModule(Runtime &runtime) {
115
+ Object module(runtime);
116
+
117
+ module.setProperty(
118
+ runtime,
119
+ "downloadAsync",
120
+ Function::createFromHostFunction(
121
+ runtime,
122
+ PropNameID::forAscii(runtime, "downloadAsync"),
123
+ 3,
124
+ [](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
125
+ if (count < 1 || !args[0].isString()) {
126
+ return rejectedPromise(rt, "downloadAsync expects a url");
127
+ }
128
+ const std::string url = args[0].asString(rt).utf8(rt);
129
+
130
+ if (url.rfind("file://", 0) != 0) {
131
+ return rejectedPromise(
132
+ rt,
133
+ "basalt-core cannot fetch assets over the network yet; "
134
+ "got " + url);
135
+ }
136
+
137
+ const std::string path = url.substr(std::string_view{"file://"}.size());
138
+ std::error_code ignored;
139
+ if (!std::filesystem::is_regular_file(path, ignored)) {
140
+ return rejectedPromise(
141
+ rt,
142
+ "no asset at " + path +
143
+ ". Assets are not copied beside the bundle yet.");
144
+ }
145
+
146
+ return rt.global()
147
+ .getPropertyAsObject(rt, "Promise")
148
+ .getPropertyAsFunction(rt, "resolve")
149
+ .call(rt, String::createFromUtf8(rt, url));
150
+ }));
151
+
152
+ return module;
153
+ }
154
+
155
+ // ExpoFontLoader, as far as a non-web platform is concerned. expo-font's own
156
+ // type declaration marks everything but these two as web-only.
157
+ Object makeFontLoader(Runtime &runtime) {
158
+ Object loader(runtime);
159
+
160
+ loader.setProperty(
161
+ runtime,
162
+ "loadAsync",
163
+ Function::createFromHostFunction(
164
+ runtime,
165
+ PropNameID::forAscii(runtime, "loadAsync"),
166
+ 2,
167
+ [](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
168
+ if (count < 2 || !args[0].isString()) {
169
+ return rejectedPromise(rt, "loadAsync expects a family name and a font");
170
+ }
171
+ const std::string name = args[0].asString(rt).utf8(rt);
172
+ const std::string path = localPathFrom(rt, args[1]);
173
+ if (path.empty()) {
174
+ return rejectedPromise(
175
+ rt, "could not find a local file for font '" + name + "'");
176
+ }
177
+ if (!registerFont(name, path)) {
178
+ return rejectedPromise(rt, "could not load font '" + name + "' from " + path);
179
+ }
180
+ return resolvedPromise(rt);
181
+ }));
182
+
183
+ loader.setProperty(
184
+ runtime,
185
+ "isLoaded",
186
+ Function::createFromHostFunction(
187
+ runtime,
188
+ PropNameID::forAscii(runtime, "isLoaded"),
189
+ 1,
190
+ [](Runtime &rt, const Value &, const Value *args, size_t count) -> Value {
191
+ if (count < 1 || !args[0].isString()) {
192
+ return Value(false);
193
+ }
194
+ return Value(isFontRegistered(args[0].asString(rt).utf8(rt)));
195
+ }));
196
+
197
+ loader.setProperty(
198
+ runtime,
199
+ "getLoadedFonts",
200
+ Function::createFromHostFunction(
201
+ runtime,
202
+ PropNameID::forAscii(runtime, "getLoadedFonts"),
203
+ 0,
204
+ [](Runtime &rt, const Value &, const Value *, size_t) -> Value {
205
+ Object array = rt.global()
206
+ .getPropertyAsFunction(rt, "Array")
207
+ .callAsConstructor(rt)
208
+ .asObject(rt);
209
+ Function push = array.getPropertyAsFunction(rt, "push");
210
+
211
+ const std::string joined = registeredFontNamesJoined('\n');
212
+ std::size_t start = 0;
213
+ while (start < joined.size()) {
214
+ const std::size_t end = joined.find('\n', start);
215
+ const std::string one =
216
+ joined.substr(start, end == std::string::npos ? std::string::npos : end - start);
217
+ push.callWithThis(rt, array, String::createFromUtf8(rt, one));
218
+ if (end == std::string::npos) {
219
+ break;
220
+ }
221
+ start = end + 1;
222
+ }
223
+ return Value(rt, array);
224
+ }));
225
+
226
+ return loader;
227
+ }
228
+
229
+ // `expo/fetch`, which this platform does not have, routed back to the one it
230
+ // does.
231
+ //
232
+ // Expo replaces `globalThis.fetch` with its own WinterCG implementation, built
233
+ // on a native module called `ExpoFetchModule` -- a pair of SharedObject classes
234
+ // with a streaming body between them. There is no port of it here, so the
235
+ // replacement is a lazy getter that throws `Cannot find native module
236
+ // 'ExpoFetchModule'` the first time anything in the app calls `fetch`. That is
237
+ // most apps, immediately, and it is not a failure an app can catch: it happens
238
+ // inside the global it was calling.
239
+ //
240
+ // Expo's own way out is `EXPO_PUBLIC_USE_RN_FETCH`, which leaves React Native's
241
+ // fetch in place -- and React Native's fetch works here, over the same curl
242
+ // client core/HttpClient.cpp gives the rest of the networking stack. So the
243
+ // default is set here rather than asked of every app, because "install basalt
244
+ // and the desktop works" stops being true if each app has to discover this
245
+ // variable from a crash inside `fetch`.
246
+ //
247
+ // **Before the bundle, so that everything downstream agrees.** Metro's prelude
248
+ // opens with `process = globalThis.process || {}` and React Native's
249
+ // setUpGlobals with `global.process = global.process || {}`, both of which keep
250
+ // what is already there. Expo's CLI then defines the app's own `EXPO_PUBLIC_`
251
+ // variables over the top in development, which is the right precedence: a
252
+ // developer who sets this explicitly beats the default. Plain assignment rather
253
+ // than defineProperty for that reason -- a non-configurable property here would
254
+ // make their `Object.defineProperties` throw.
255
+ //
256
+ // What is lost is what `expo/fetch` adds: a streamed response body. React
257
+ // Native's fetch buffers, and an app importing `expo/fetch` directly still gets
258
+ // the error naming the module, which is the honest answer until someone ports
259
+ // it.
260
+ void useReactNativeFetch(facebook::jsi::Runtime &runtime) {
261
+ namespace jsi = facebook::jsi;
262
+ static constexpr const char *kVariable = "EXPO_PUBLIC_USE_RN_FETCH";
263
+
264
+ jsi::Object global = runtime.global();
265
+ const jsi::Value processValue = global.getProperty(runtime, "process");
266
+ jsi::Object process =
267
+ processValue.isObject() ? processValue.asObject(runtime) : jsi::Object(runtime);
268
+
269
+ const jsi::Value envValue = process.getProperty(runtime, "env");
270
+ jsi::Object env = envValue.isObject() ? envValue.asObject(runtime) : jsi::Object(runtime);
271
+
272
+ // Only as a default. Nothing sets this before the bundle today, but something
273
+ // that did would have meant it.
274
+ if (env.getProperty(runtime, kVariable).isUndefined()) {
275
+ env.setProperty(runtime, kVariable, jsi::String::createFromAscii(runtime, "1"));
276
+ }
277
+
278
+ process.setProperty(runtime, "env", std::move(env));
279
+ global.setProperty(runtime, "process", std::move(process));
280
+ }
281
+
282
+ } // namespace
283
+
284
+ void installExpoRuntime(facebook::jsi::Runtime &runtime) {
285
+ namespace jsi = facebook::jsi;
286
+
287
+ // `expo` has to exist before the classes go in: each of them reaches for
288
+ // `global.expo` through expo::common::getCoreObject and hangs itself off it.
289
+ // Installing them first throws on a property that is not there yet.
290
+ runtime.global().setProperty(runtime, "expo", jsi::Object(runtime));
291
+
292
+ expo::EventEmitter::installClass(runtime);
293
+
294
+ // The releaser is how Expo hands a shared object's native half back to be
295
+ // freed. On Android it reaches through JNI to the owning context. Nothing
296
+ // here owns any native Expo object -- no Expo module is ported yet -- so
297
+ // there is nothing to free, and the honest implementation of "free nothing"
298
+ // is to do nothing.
299
+ expo::SharedObject::installBaseClass(runtime, [](expo::SharedObject::ObjectId) {});
300
+ expo::SharedRef::installBaseClass(runtime);
301
+ expo::NativeModule::installClass(runtime);
302
+
303
+ // The module registry.
304
+ //
305
+ // ExpoAsset is here rather than in ExpoModules.cpp because it is older than
306
+ // the module host and because it is half a stub: it confirms a `file://`
307
+ // asset exists and rejects an http one, which is enough for Expo's own
308
+ // start-up and no more. ExponentConstants used to sit beside it as an empty
309
+ // object for the same reason, and is a real module now.
310
+ jsi::Object modules(runtime);
311
+ modules.setProperty(runtime, "ExpoAsset", makeAssetModule(runtime));
312
+
313
+ // The first Expo module here that is not a stub. It loads a real font file
314
+ // into fontconfig, which is where Pango looks, so `fontFamily` works
315
+ // afterwards for text this platform renders. See src/LinuxFonts.cpp.
316
+ modules.setProperty(runtime, "ExpoFontLoader", makeFontLoader(runtime));
317
+
318
+ // This platform's own Expo modules. See core/ExpoModules.h; they are separate
319
+ // from the stubs above because they are not stubs.
320
+ installExpoModules(runtime, modules);
321
+
322
+ // expo-modules-core builds its NativeModulesProxy by iterating this, falling
323
+ // back to the legacy proxy when it is absent, so its presence matters even
324
+ // when it is empty.
325
+ jsi::Object proxy(runtime);
326
+ proxy.setProperty(runtime, "exportedMethods", jsi::Object(runtime));
327
+ proxy.setProperty(runtime, "viewManagersMetadata", jsi::Object(runtime));
328
+ modules.setProperty(runtime, "NativeModulesProxy", std::move(proxy));
329
+
330
+ jsi::Object core = runtime.global().getPropertyAsObject(runtime, "expo");
331
+ core.setProperty(runtime, "modules", std::move(modules));
332
+ installExpoViewConfigs(runtime, core);
333
+
334
+ // Before the bundle evaluates, which is the only moment early enough; see the
335
+ // function.
336
+ useReactNativeFetch(runtime);
337
+
338
+ // `expo.uuidv4`, which expo-modules-core's own `uuid.v4()` calls and throws
339
+ // without: "Native UUID version 4 generator implementation wasn't found in
340
+ // `expo-modules-core`". It is not a notifications concern, though that is
341
+ // where it was found -- `scheduleNotificationAsync` names a notification with
342
+ // one when an app does not, and so does every other Expo package that
343
+ // generates an identifier.
344
+ //
345
+ // `uuidv5` is deliberately absent: it is a SHA-1 of a namespace and a name,
346
+ // which is a hash implementation rather than a random number, and expo
347
+ // reports a missing one by name the way it reports a missing method.
348
+ core.setProperty(
349
+ runtime,
350
+ "uuidv4",
351
+ jsi::Function::createFromHostFunction(
352
+ runtime,
353
+ jsi::PropNameID::forAscii(runtime, "uuidv4"),
354
+ 0,
355
+ [](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value {
356
+ return jsi::String::createFromUtf8(rt, randomUuidV4());
357
+ }));
358
+ }
359
+
360
+ #else
361
+
362
+ void installExpoRuntime(facebook::jsi::Runtime & /*runtime*/) {}
363
+
364
+ #endif
365
+
366
+ } // namespace basalt
@@ -0,0 +1,42 @@
1
+ // Expo's runtime, installed into the JavaScript runtime before the bundle runs.
2
+ //
3
+ // An Expo app's very first import reaches `globalThis.expo.EventEmitter`, and
4
+ // without it the app dies before React renders. That object is normally
5
+ // installed from native code, which is why an Expo app cannot start on a
6
+ // platform nobody has ported Expo to.
7
+ //
8
+ // The port is smaller than it sounds, because expo-modules-core ships the
9
+ // portable half in its npm package: `common/cpp` holds EventEmitter,
10
+ // SharedObject, SharedRef, NativeModule and their JSI helpers, and depends on
11
+ // nothing but JSI and a call invoker. So there is nothing to vendor and nothing
12
+ // to fetch -- this compiles the app's own copy, at the app's own version, the
13
+ // same arrangement that keeps React Native's C++ and JavaScript in step.
14
+ //
15
+ // react-native-windows and react-native-macos reach the same place by way of
16
+ // shirakaba/expo-desktop, which vendors that directory at a pinned Expo SDK.
17
+ // Compiling the app's copy avoids the pin.
18
+ //
19
+ // What this installs is deliberately the floor rather than the ceiling: the
20
+ // four classes and an empty module registry, which is what an Expo app needs to
21
+ // *start*. Any actual Expo module -- expo-font, expo-image, a config plugin's
22
+ // native half -- needs its own port, and this does not pretend otherwise.
23
+
24
+ #pragma once
25
+
26
+ #include <jsi/jsi.h>
27
+
28
+ namespace basalt {
29
+
30
+ // Installs `globalThis.expo`. Call before the bundle is evaluated; ReactHost's
31
+ // bindingsInstallFunc is that moment.
32
+ //
33
+ // Compiled only when the build was pointed at an expo-modules-core; without one
34
+ // this is a no-op, so a plain React Native app pays nothing and an Expo app
35
+ // fails the way it did before rather than differently.
36
+ void installExpoRuntime(facebook::jsi::Runtime &runtime);
37
+
38
+ // Whether the above will do anything, for the log line that tells a developer
39
+ // which of the two situations they are in.
40
+ bool hasExpoRuntime();
41
+
42
+ } // namespace basalt
@@ -0,0 +1,36 @@
1
+ // What keyboard focus looks like.
2
+ //
3
+ // Two numbers and a colour, in one place, for the same reason
4
+ // `kWheelStepPixels` is: a ring that is two pixels of blue on one desktop and
5
+ // three of grey on another is a difference an app did not ask for and cannot
6
+ // control. Matching the other platform matters more here than matching either
7
+ // toolkit's own default, which is the argument of this project in one constant.
8
+ //
9
+ // Not the system accent colour, on either desktop, and that is a real choice
10
+ // rather than an omission. macOS has `NSColor.keyboardFocusIndicatorColor` and
11
+ // GTK has a theme; using each would make the ring native and make the two
12
+ // desktops disagree, and an app that draws its own buttons in its own colours
13
+ // has already said it is not trying to look native. The same reasoning kept
14
+ // `<TextInput>`'s peers looking like the app rather than like the toolkit.
15
+ //
16
+ // Drawn rather than delegated for a second reason too: these widgets have no
17
+ // theme to delegate to. React Native decided every colour in the tree, and GTK's
18
+ // focus rendering is a CSS outline on a themed widget.
19
+
20
+ #pragma once
21
+
22
+ namespace basalt {
23
+
24
+ // Pixels, outside the view's own bounds, so the ring does not cover content at
25
+ // the edge of a button.
26
+ constexpr float kFocusRingWidth = 2.0F;
27
+
28
+ // A blue that reads as "focused" on a light background and on a dark one, which
29
+ // is what an app-coloured tree offers no guarantees about. sRGB, 0..1 per
30
+ // channel, in the order every host's colour type takes.
31
+ constexpr float kFocusRingRed = 0.259F;
32
+ constexpr float kFocusRingGreen = 0.522F;
33
+ constexpr float kFocusRingBlue = 0.957F;
34
+ constexpr float kFocusRingAlpha = 1.0F;
35
+
36
+ } // namespace basalt
@@ -0,0 +1,51 @@
1
+ // Fonts an app loads at runtime, and the name it knows them by.
2
+ //
3
+ // Declared here and implemented per platform, because this is one of the places
4
+ // a desktop platform genuinely differs: fontconfig on Linux, and something else
5
+ // on macOS or Windows. Core calls it -- `expo-font` arrives here -- and links
6
+ // against whichever implementation the platform half provides. Linux's is
7
+ // src/FontRegistryFontconfig.cpp.
8
+ //
9
+ // `expo-font` hands over a file and a name of the app's choosing -- "Inter",
10
+ // say -- and expects `fontFamily: 'Inter'` to work afterwards. A font file
11
+ // carries its own family name inside it, which is usually something else
12
+ // entirely, and fontconfig indexes by that. So loading the file is only half
13
+ // the job; the other half is remembering that this app calls it "Inter".
14
+ //
15
+ // Kept here rather than in the Expo layer because the Pango side has to consult
16
+ // it on every text measurement, and because nothing about it is Expo-specific:
17
+ // any future API that loads a font at runtime wants exactly this.
18
+
19
+ #pragma once
20
+
21
+ #include <string>
22
+
23
+ namespace basalt {
24
+
25
+ // Makes a font file available to Pango, under `name`.
26
+ //
27
+ // Returns false if the file cannot be read or fontconfig will not take it, in
28
+ // which case nothing is registered. Safe to call twice with the same name.
29
+ bool registerFont(const std::string &name, const std::string &path);
30
+
31
+ // The family Pango should be asked for, given what the app called it. Returns
32
+ // `name` unchanged when it was never registered, so ordinary system families
33
+ // pass straight through.
34
+ std::string resolveFontFamily(const std::string &name);
35
+
36
+ // Bumped every time a font is registered. Text measured before a font arrived
37
+ // was measured against a different set of fonts, and a cache that does not know
38
+ // that will keep handing back the old size -- which is what makes a font loaded
39
+ // after first render appear to do nothing.
40
+ unsigned long fontGeneration();
41
+
42
+ // Whether `name` has been registered. expo-font insists this exists and is a
43
+ // function, and throws when it is not, so it is not optional however web-only
44
+ // its type declaration claims to be.
45
+ bool isFontRegistered(const std::string &name);
46
+
47
+ // The names registered so far, in registration order, for
48
+ // ExpoFontLoader.getLoadedFonts.
49
+ std::string registeredFontNamesJoined(char separator);
50
+
51
+ } // namespace basalt
@@ -0,0 +1,136 @@
1
+ #include "GestureHandlerModule.h"
2
+
3
+ #include "Gestures.h"
4
+ #include "PlatformServices.h"
5
+
6
+ #include <jsi/JSIDynamic.h>
7
+
8
+ namespace basalt {
9
+
10
+ using facebook::jsi::Runtime;
11
+ using facebook::jsi::Value;
12
+ using facebook::react::TurboModule;
13
+
14
+ namespace {
15
+
16
+ double numberArg(const Value *args, size_t count, size_t index) {
17
+ return index < count && args[index].isNumber() ? args[index].asNumber() : 0;
18
+ }
19
+
20
+ folly::dynamic objectArg(Runtime &runtime, const Value *args, size_t count, size_t index) {
21
+ if (index >= count || !args[index].isObject()) {
22
+ return folly::dynamic::object();
23
+ }
24
+ return facebook::jsi::dynamicFromValue(runtime, args[index]);
25
+ }
26
+
27
+ } // namespace
28
+
29
+ DesktopGestureHandlerModule::DesktopGestureHandlerModule(
30
+ std::shared_ptr<facebook::react::CallInvoker> jsInvoker)
31
+ : TurboModule(kModuleName, std::move(jsInvoker)) {
32
+ methodMap_["createGestureHandler"] = MethodMetadata{3, createGestureHandler};
33
+ methodMap_["attachGestureHandler"] = MethodMetadata{3, attachGestureHandler};
34
+ methodMap_["updateGestureHandler"] = MethodMetadata{2, updateGestureHandler};
35
+ methodMap_["dropGestureHandler"] = MethodMetadata{1, dropGestureHandler};
36
+ methodMap_["install"] = MethodMetadata{0, install};
37
+ // Nothing here batches, so there is nothing to flush; and there is no
38
+ // responder to hand over, because this platform's gesture recognisers and
39
+ // React Native's touches are fed from the same place rather than competing
40
+ // for a responder. See core/Gestures.h.
41
+ methodMap_["flushOperations"] = MethodMetadata{0, noop};
42
+ methodMap_["handleSetJSResponder"] = MethodMetadata{2, noop};
43
+ methodMap_["handleClearJSResponder"] = MethodMetadata{0, noop};
44
+
45
+ // The registry outlives any one instance of this module -- it is UI-thread
46
+ // state and the module is recreated whenever the runtime is -- so the
47
+ // emitter is installed here and taken away in the destructor rather than
48
+ // left pointing at a module that has gone.
49
+ gestures().setEmitter([this](const std::string &eventName, folly::dynamic payload) {
50
+ emitGestureEvent(eventName, std::move(payload));
51
+ });
52
+ }
53
+
54
+ DesktopGestureHandlerModule::~DesktopGestureHandlerModule() {
55
+ gestures().setEmitter(nullptr);
56
+ }
57
+
58
+ void DesktopGestureHandlerModule::emitGestureEvent(const std::string &eventName,
59
+ folly::dynamic payload) {
60
+ emitDeviceEvent(eventName,
61
+ [payload = std::move(payload)](Runtime &runtime, std::vector<Value> &args) {
62
+ args.emplace_back(facebook::jsi::valueFromDynamic(runtime, payload));
63
+ });
64
+ }
65
+
66
+ Value DesktopGestureHandlerModule::createGestureHandler(Runtime &runtime,
67
+ TurboModule & /*module*/,
68
+ const Value *args,
69
+ size_t count) {
70
+ if (count < 2 || !args[0].isString()) {
71
+ return Value::undefined();
72
+ }
73
+ std::string name = args[0].asString(runtime).utf8(runtime);
74
+ const int tag = static_cast<int>(numberArg(args, count, 1));
75
+ folly::dynamic config = objectArg(runtime, args, count, 2);
76
+
77
+ postToUiThread([name = std::move(name), tag, config = std::move(config)]() mutable {
78
+ gestures().create(name, tag, std::move(config));
79
+ });
80
+ return Value::undefined();
81
+ }
82
+
83
+ Value DesktopGestureHandlerModule::attachGestureHandler(Runtime & /*runtime*/,
84
+ TurboModule & /*module*/,
85
+ const Value *args,
86
+ size_t count) {
87
+ const int handlerTag = static_cast<int>(numberArg(args, count, 0));
88
+ const int viewTag = static_cast<int>(numberArg(args, count, 1));
89
+ // The third argument is RNGH's ActionType. 1 is REANIMATED_WORKLET, which
90
+ // means the callbacks are worklets and the events are meant for a worklet
91
+ // runtime rather than for DeviceEventEmitter. Attaching it anyway is right:
92
+ // the handler still runs, and its events are still emitted; what a worklet
93
+ // gesture loses is being handled off the JS thread.
94
+ postToUiThread([handlerTag, viewTag]() { gestures().attach(handlerTag, viewTag); });
95
+ return Value::undefined();
96
+ }
97
+
98
+ Value DesktopGestureHandlerModule::updateGestureHandler(Runtime &runtime,
99
+ TurboModule & /*module*/,
100
+ const Value *args,
101
+ size_t count) {
102
+ const int handlerTag = static_cast<int>(numberArg(args, count, 0));
103
+ folly::dynamic config = objectArg(runtime, args, count, 1);
104
+ postToUiThread([handlerTag, config = std::move(config)]() mutable {
105
+ gestures().update(handlerTag, std::move(config));
106
+ });
107
+ return Value::undefined();
108
+ }
109
+
110
+ Value DesktopGestureHandlerModule::dropGestureHandler(Runtime & /*runtime*/,
111
+ TurboModule & /*module*/,
112
+ const Value *args,
113
+ size_t count) {
114
+ const int handlerTag = static_cast<int>(numberArg(args, count, 0));
115
+ postToUiThread([handlerTag]() { gestures().drop(handlerTag); });
116
+ return Value::undefined();
117
+ }
118
+
119
+ // True means "the JSI bindings are in place". RNGH calls this once, on the
120
+ // first render of a GestureHandlerRootView under Fabric, and uses the answer
121
+ // only to avoid calling it again.
122
+ Value DesktopGestureHandlerModule::install(Runtime & /*runtime*/,
123
+ TurboModule & /*module*/,
124
+ const Value * /*args*/,
125
+ size_t /*count*/) {
126
+ return Value(true);
127
+ }
128
+
129
+ Value DesktopGestureHandlerModule::noop(Runtime & /*runtime*/,
130
+ TurboModule & /*module*/,
131
+ const Value * /*args*/,
132
+ size_t /*count*/) {
133
+ return Value::undefined();
134
+ }
135
+
136
+ } // namespace basalt