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.
- package/LICENSE +21 -0
- package/dist/cli/basalt.d.ts +3 -0
- package/dist/cli/basalt.d.ts.map +1 -0
- package/dist/cli/basalt.js +47 -0
- package/dist/cli/basalt.js.map +1 -0
- package/dist/cli/bundle-cli.d.ts +12 -0
- package/dist/cli/bundle-cli.d.ts.map +1 -0
- package/dist/cli/bundle-cli.js +124 -0
- package/dist/cli/bundle-cli.js.map +1 -0
- package/dist/cli/bundleWithAssets.d.ts +79 -0
- package/dist/cli/bundleWithAssets.d.ts.map +1 -0
- package/dist/cli/bundleWithAssets.js +198 -0
- package/dist/cli/bundleWithAssets.js.map +1 -0
- package/dist/cli/desktop.d.ts +339 -0
- package/dist/cli/desktop.d.ts.map +1 -0
- package/dist/cli/desktop.js +1029 -0
- package/dist/cli/desktop.js.map +1 -0
- package/dist/cli/doctor.d.ts +31 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +283 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/init.d.ts +77 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +442 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/metro.d.ts +49 -0
- package/dist/cli/metro.d.ts.map +1 -0
- package/dist/cli/metro.js +148 -0
- package/dist/cli/metro.js.map +1 -0
- package/dist/cli/packageApp.d.ts +102 -0
- package/dist/cli/packageApp.d.ts.map +1 -0
- package/dist/cli/packageApp.js +319 -0
- package/dist/cli/packageApp.js.map +1 -0
- package/dist/cli/steps.d.ts +34 -0
- package/dist/cli/steps.d.ts.map +1 -0
- package/dist/cli/steps.js +53 -0
- package/dist/cli/steps.js.map +1 -0
- package/dist/metro-config.d.ts +168 -0
- package/dist/metro-config.d.ts.map +1 -0
- package/dist/metro-config.js +671 -0
- package/dist/metro-config.js.map +1 -0
- package/dist/react-native.config.d.ts +7 -0
- package/dist/react-native.config.d.ts.map +1 -0
- package/dist/react-native.config.js +80 -0
- package/dist/react-native.config.js.map +1 -0
- package/dist/src/DragSource.d.ts +52 -0
- package/dist/src/DragSource.d.ts.map +1 -0
- package/dist/src/DragSource.js +97 -0
- package/dist/src/DragSource.js.map +1 -0
- package/dist/src/DropTarget.d.ts +58 -0
- package/dist/src/DropTarget.d.ts.map +1 -0
- package/dist/src/DropTarget.js +131 -0
- package/dist/src/DropTarget.js.map +1 -0
- package/dist/src/KeyHandler.d.ts +57 -0
- package/dist/src/KeyHandler.d.ts.map +1 -0
- package/dist/src/KeyHandler.js +48 -0
- package/dist/src/KeyHandler.js.map +1 -0
- package/dist/src/Menu.d.ts +72 -0
- package/dist/src/Menu.d.ts.map +1 -0
- package/dist/src/Menu.js +179 -0
- package/dist/src/Menu.js.map +1 -0
- package/dist/src/TitleBar.d.ts +75 -0
- package/dist/src/TitleBar.d.ts.map +1 -0
- package/dist/src/TitleBar.js +236 -0
- package/dist/src/TitleBar.js.map +1 -0
- package/dist/src/Window.d.ts +87 -0
- package/dist/src/Window.d.ts.map +1 -0
- package/dist/src/Window.js +248 -0
- package/dist/src/Window.js.map +1 -0
- package/dist/src/closeRequest.d.ts +55 -0
- package/dist/src/closeRequest.d.ts.map +1 -0
- package/dist/src/closeRequest.js +134 -0
- package/dist/src/closeRequest.js.map +1 -0
- package/dist/src/index.d.ts +45 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +92 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/overrides/Alert.d.ts +69 -0
- package/dist/src/overrides/Alert.d.ts.map +1 -0
- package/dist/src/overrides/Alert.js +112 -0
- package/dist/src/overrides/Alert.js.map +1 -0
- package/dist/src/overrides/BaseViewConfig.d.ts +28 -0
- package/dist/src/overrides/BaseViewConfig.d.ts.map +1 -0
- package/dist/src/overrides/BaseViewConfig.js +72 -0
- package/dist/src/overrides/BaseViewConfig.js.map +1 -0
- package/dist/src/overrides/Platform.linux.d.ts +10 -0
- package/dist/src/overrides/Platform.linux.d.ts.map +1 -0
- package/dist/src/overrides/Platform.linux.js +15 -0
- package/dist/src/overrides/Platform.linux.js.map +1 -0
- package/dist/src/overrides/Platform.macos.d.ts +10 -0
- package/dist/src/overrides/Platform.macos.d.ts.map +1 -0
- package/dist/src/overrides/Platform.macos.js +15 -0
- package/dist/src/overrides/Platform.macos.js.map +1 -0
- package/dist/src/overrides/Platform.windows.d.ts +10 -0
- package/dist/src/overrides/Platform.windows.d.ts.map +1 -0
- package/dist/src/overrides/Platform.windows.js +15 -0
- package/dist/src/overrides/Platform.windows.js.map +1 -0
- package/dist/src/overrides/ReactDevToolsSettingsManager.d.ts +24 -0
- package/dist/src/overrides/ReactDevToolsSettingsManager.d.ts.map +1 -0
- package/dist/src/overrides/ReactDevToolsSettingsManager.js +30 -0
- package/dist/src/overrides/ReactDevToolsSettingsManager.js.map +1 -0
- package/dist/src/overrides/RefreshControl.d.ts +52 -0
- package/dist/src/overrides/RefreshControl.d.ts.map +1 -0
- package/dist/src/overrides/RefreshControl.js +92 -0
- package/dist/src/overrides/RefreshControl.js.map +1 -0
- package/dist/src/overrides/ScrollView.d.ts +39 -0
- package/dist/src/overrides/ScrollView.d.ts.map +1 -0
- package/dist/src/overrides/ScrollView.js +59 -0
- package/dist/src/overrides/ScrollView.js.map +1 -0
- package/dist/src/overrides/Share.d.ts +96 -0
- package/dist/src/overrides/Share.d.ts.map +1 -0
- package/dist/src/overrides/Share.js +97 -0
- package/dist/src/overrides/Share.js.map +1 -0
- package/dist/src/overrides/TextInput.d.ts +81 -0
- package/dist/src/overrides/TextInput.d.ts.map +1 -0
- package/dist/src/overrides/TextInput.js +213 -0
- package/dist/src/overrides/TextInput.js.map +1 -0
- package/dist/src/overrides/View.d.ts +36 -0
- package/dist/src/overrides/View.d.ts.map +1 -0
- package/dist/src/overrides/View.js +99 -0
- package/dist/src/overrides/View.js.map +1 -0
- package/dist/src/overrides/createPlatform.d.ts +32 -0
- package/dist/src/overrides/createPlatform.d.ts.map +1 -0
- package/dist/src/overrides/createPlatform.js +84 -0
- package/dist/src/overrides/createPlatform.js.map +1 -0
- package/dist/src/overrides/setUpDeveloperTools.d.ts +28 -0
- package/dist/src/overrides/setUpDeveloperTools.d.ts.map +1 -0
- package/dist/src/overrides/setUpDeveloperTools.js +84 -0
- package/dist/src/overrides/setUpDeveloperTools.js.map +1 -0
- package/dist/src/overrides/setUpXHR.d.ts +46 -0
- package/dist/src/overrides/setUpXHR.d.ts.map +1 -0
- package/dist/src/overrides/setUpXHR.js +236 -0
- package/dist/src/overrides/setUpXHR.js.map +1 -0
- package/dist/src/quitRequest.d.ts +55 -0
- package/dist/src/quitRequest.d.ts.map +1 -0
- package/dist/src/quitRequest.js +121 -0
- package/dist/src/quitRequest.js.map +1 -0
- package/dist/src/resourcePath.d.ts +18 -0
- package/dist/src/resourcePath.d.ts.map +1 -0
- package/dist/src/resourcePath.js +31 -0
- package/dist/src/resourcePath.js.map +1 -0
- package/dist/src/responseBody.d.ts +76 -0
- package/dist/src/responseBody.d.ts.map +1 -0
- package/dist/src/responseBody.js +94 -0
- package/dist/src/responseBody.js.map +1 -0
- package/dist/src/responseHeaders.d.ts +40 -0
- package/dist/src/responseHeaders.d.ts.map +1 -0
- package/dist/src/responseHeaders.js +75 -0
- package/dist/src/responseHeaders.js.map +1 -0
- package/dist/src/titleBarState.d.ts +43 -0
- package/dist/src/titleBarState.d.ts.map +1 -0
- package/dist/src/titleBarState.js +77 -0
- package/dist/src/titleBarState.js.map +1 -0
- package/dist/src/useContextMenu.d.ts +113 -0
- package/dist/src/useContextMenu.d.ts.map +1 -0
- package/dist/src/useContextMenu.js +109 -0
- package/dist/src/useContextMenu.js.map +1 -0
- package/dist/src/useDialog.d.ts +98 -0
- package/dist/src/useDialog.d.ts.map +1 -0
- package/dist/src/useDialog.js +112 -0
- package/dist/src/useDialog.js.map +1 -0
- package/dist/src/useDisplays.d.ts +70 -0
- package/dist/src/useDisplays.d.ts.map +1 -0
- package/dist/src/useDisplays.js +118 -0
- package/dist/src/useDisplays.js.map +1 -0
- package/dist/src/useHandledKeys.d.ts +38 -0
- package/dist/src/useHandledKeys.d.ts.map +1 -0
- package/dist/src/useHandledKeys.js +70 -0
- package/dist/src/useHandledKeys.js.map +1 -0
- package/dist/src/useWindow.d.ts +140 -0
- package/dist/src/useWindow.d.ts.map +1 -0
- package/dist/src/useWindow.js +233 -0
- package/dist/src/useWindow.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/native/CMakeLists.txt +355 -0
- package/native/bootstrap.sh +613 -0
- package/native/cmake/CompilerFlags.cmake +49 -0
- package/native/cmake/Expo.cmake +125 -0
- package/native/cmake/ReactNativeCore.cmake +612 -0
- package/native/cmake/Reanimated.cmake +108 -0
- package/native/cmake/Skia.cmake +379 -0
- package/native/cmake/ThirdParty.cmake +290 -0
- package/native/cmake/Worklets.cmake +67 -0
- package/native/cmake/codegen/CMakeLists.txt +32 -0
- package/native/core/AppIdentity.cpp +85 -0
- package/native/core/AppIdentity.h +59 -0
- package/native/core/AppPaths.cpp +96 -0
- package/native/core/AppPaths.h +53 -0
- package/native/core/AppearanceModule.cpp +60 -0
- package/native/core/AppearanceModule.h +40 -0
- package/native/core/Backface.h +59 -0
- package/native/core/BlobModule.cpp +283 -0
- package/native/core/BlobModule.h +76 -0
- package/native/core/BlobRegistry.cpp +75 -0
- package/native/core/BlobRegistry.h +44 -0
- package/native/core/ColorScheme.cpp +105 -0
- package/native/core/ColorScheme.h +59 -0
- package/native/core/ComponentRegistry.h +30 -0
- package/native/core/ControlMetrics.h +37 -0
- package/native/core/CoreModules.cpp +592 -0
- package/native/core/CoreModules.h +308 -0
- package/native/core/DebuggingOverlay.cpp +106 -0
- package/native/core/DebuggingOverlay.h +65 -0
- package/native/core/DesktopControls.cpp +232 -0
- package/native/core/DesktopControls.h +170 -0
- package/native/core/DevBundle.cpp +108 -0
- package/native/core/DevBundle.h +69 -0
- package/native/core/DevMenu.cpp +99 -0
- package/native/core/DevMenu.h +75 -0
- package/native/core/DialogModule.cpp +150 -0
- package/native/core/DialogModule.h +51 -0
- package/native/core/DragAndDrop.cpp +45 -0
- package/native/core/DragAndDrop.h +225 -0
- package/native/core/ExpoImageComponent.cpp +79 -0
- package/native/core/ExpoImageComponent.h +80 -0
- package/native/core/ExpoModules.cpp +415 -0
- package/native/core/ExpoModules.h +115 -0
- package/native/core/ExpoRuntime.cpp +366 -0
- package/native/core/ExpoRuntime.h +42 -0
- package/native/core/FocusRing.h +36 -0
- package/native/core/FontRegistry.h +51 -0
- package/native/core/GestureHandlerModule.cpp +136 -0
- package/native/core/GestureHandlerModule.h +71 -0
- package/native/core/Gestures.cpp +533 -0
- package/native/core/Gestures.h +145 -0
- package/native/core/HoverTracker.cpp +69 -0
- package/native/core/HoverTracker.h +109 -0
- package/native/core/HttpClient.cpp +282 -0
- package/native/core/ImageBytes.cpp +205 -0
- package/native/core/ImageBytes.h +29 -0
- package/native/core/ImageCache.cpp +51 -0
- package/native/core/ImageCache.h +89 -0
- package/native/core/JsRuntimeAccess.cpp +70 -0
- package/native/core/JsRuntimeAccess.h +60 -0
- package/native/core/JsiPromise.h +51 -0
- package/native/core/KeyEvents.cpp +116 -0
- package/native/core/KeyEvents.h +144 -0
- package/native/core/LogBoxSurface.cpp +46 -0
- package/native/core/LogBoxSurface.h +68 -0
- package/native/core/MenuModel.h +117 -0
- package/native/core/MenuModule.cpp +209 -0
- package/native/core/MenuModule.h +66 -0
- package/native/core/MountingWalk.h +524 -0
- package/native/core/PackageModules.cpp.in +25 -0
- package/native/core/PackageModules.h +50 -0
- package/native/core/PlatformConstantsModule.cpp +63 -0
- package/native/core/PlatformConstantsModule.h +64 -0
- package/native/core/PlatformServices.h +213 -0
- package/native/core/PointerButtons.h +63 -0
- package/native/core/PullToRefresh.cpp +57 -0
- package/native/core/PullToRefresh.h +73 -0
- package/native/core/ReanimatedCompat.h +47 -0
- package/native/core/ReanimatedModule.cpp +203 -0
- package/native/core/ReanimatedModule.h +55 -0
- package/native/core/ScrollAnimation.cpp +59 -0
- package/native/core/ScrollAnimation.h +70 -0
- package/native/core/ScrollBounds.cpp +26 -0
- package/native/core/ScrollBounds.h +68 -0
- package/native/core/ScrollIndicator.cpp +50 -0
- package/native/core/ScrollIndicator.h +69 -0
- package/native/core/ScrollMomentum.cpp +71 -0
- package/native/core/ScrollMomentum.h +73 -0
- package/native/core/ScrollSnap.cpp +123 -0
- package/native/core/ScrollSnap.h +80 -0
- package/native/core/ShareFallback.cpp +101 -0
- package/native/core/ShareFallback.h +45 -0
- package/native/core/SkiaPictureViewComponent.cpp +11 -0
- package/native/core/SkiaPictureViewComponent.h +47 -0
- package/native/core/SourceCodeModule.cpp +55 -0
- package/native/core/SourceCodeModule.h +53 -0
- package/native/core/StatusBarModule.cpp +23 -0
- package/native/core/StatusBarModule.h +42 -0
- package/native/core/TestDialog.cpp +148 -0
- package/native/core/TestDialog.h +96 -0
- package/native/core/TestKeys.cpp +78 -0
- package/native/core/TestKeys.h +50 -0
- package/native/core/TestQuitFile.cpp +33 -0
- package/native/core/TestQuitFile.h +60 -0
- package/native/core/TestSettle.h +192 -0
- package/native/core/TitleBarRegions.h +31 -0
- package/native/core/TurboModuleProxy.cpp +51 -0
- package/native/core/TurboModuleProxy.h +46 -0
- package/native/core/UIManagerAccess.cpp +43 -0
- package/native/core/UIManagerAccess.h +43 -0
- package/native/core/WindowBoundsCache.cpp +319 -0
- package/native/core/WindowControl.h +235 -0
- package/native/core/WindowHost.h +158 -0
- package/native/core/WindowMethods.cpp +167 -0
- package/native/core/WindowMethods.h +84 -0
- package/native/core/WindowsModule.cpp +448 -0
- package/native/core/WindowsModule.h +133 -0
- package/native/core/WorkletsModule.cpp +199 -0
- package/native/core/WorkletsModule.h +57 -0
- package/native/core/portability_probe.cpp +210 -0
- package/native/tests/EventRecorder.h +124 -0
- package/native/tests/TestHarness.cpp +109 -0
- package/native/tests/TestHarness.h +89 -0
- package/native/tests/TreeDump.h +49 -0
- package/native/tests/test_app_identity.cpp +114 -0
- package/native/tests/test_app_paths.cpp +48 -0
- package/native/tests/test_appearance.cpp +107 -0
- package/native/tests/test_backface.cpp +79 -0
- package/native/tests/test_blobs.cpp +157 -0
- package/native/tests/test_controls.cpp +185 -0
- package/native/tests/test_devbundle.cpp +103 -0
- package/native/tests/test_drag_and_drop.cpp +143 -0
- package/native/tests/test_gestures.cpp +331 -0
- package/native/tests/test_hover.cpp +92 -0
- package/native/tests/test_image_cache.cpp +109 -0
- package/native/tests/test_js_runtime_access.cpp +137 -0
- package/native/tests/test_key_events.cpp +235 -0
- package/native/tests/test_momentum.cpp +138 -0
- package/native/tests/test_quit_file.cpp +104 -0
- package/native/tests/test_scroll_animation.cpp +120 -0
- package/native/tests/test_scroll_bounds.cpp +66 -0
- package/native/tests/test_scroll_indicator.cpp +149 -0
- package/native/tests/test_scroll_snap.cpp +112 -0
- package/native/tests/test_settle.cpp +115 -0
- package/native/tests/test_share.cpp +75 -0
- package/native/tests/test_sourcecode.cpp +62 -0
- package/native/tests/test_window_close.cpp +119 -0
- package/native/tests/test_window_limits.cpp +123 -0
- package/package.json +43 -0
- package/react-native.config.js +86 -0
- package/supported-versions.json +66 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// Which view claims which key, and what a key is called.
|
|
2
|
+
//
|
|
3
|
+
// `onKeyPress` on a `<TextInput>` existed and a `<View>` had nothing, so an app
|
|
4
|
+
// could not bind a shortcut to anything it draws. On a phone that is no loss; on
|
|
5
|
+
// a desktop it is most of the interface.
|
|
6
|
+
//
|
|
7
|
+
// ## The vocabulary is a browser's
|
|
8
|
+
//
|
|
9
|
+
// A key is named the way `KeyboardEvent.key` names it: `"a"`, `"A"`, `" "`,
|
|
10
|
+
// `"ArrowLeft"`, `"Escape"`, `"F5"`. react-native-macos and react-native-windows
|
|
11
|
+
// both use these, so an app that binds `"ArrowLeft"` means the same thing on all
|
|
12
|
+
// five platforms it might run on. The names live here as constants rather than as
|
|
13
|
+
// string literals in three hosts, for the reason core/TestQuitFile.h gives about
|
|
14
|
+
// shared spellings: three places to write "ArrowLeft" is three places to write
|
|
15
|
+
// "ArrowLeft " instead, and the failure is a shortcut that silently never fires.
|
|
16
|
+
//
|
|
17
|
+
// Case follows the character, as it does in a browser: Shift+A is `"A"` and its
|
|
18
|
+
// `shift` modifier is also set. An app may bind either and both are honest.
|
|
19
|
+
//
|
|
20
|
+
// ## Why a view declares its keys in advance
|
|
21
|
+
//
|
|
22
|
+
// Because a host has to decide whether a key was handled *before* it can do
|
|
23
|
+
// anything else, and it cannot ask JavaScript at that moment. An unhandled key
|
|
24
|
+
// must still reach the application menu, a focused text field, or a scroll view
|
|
25
|
+
// that scrolls on arrows; a handled one must not. That decision is the return
|
|
26
|
+
// value of `performKeyEquivalent:`, of GTK's `key-pressed` handler, and of
|
|
27
|
+
// whether `WM_KEYDOWN` reaches `DefWindowProc` -- three synchronous answers, and
|
|
28
|
+
// a round trip through the JavaScript thread is not available to any of them.
|
|
29
|
+
//
|
|
30
|
+
// So `handledBy` is the whole point of this file: a host asks it, gets an answer
|
|
31
|
+
// in the same call stack, and consumes the key or passes it on accordingly.
|
|
32
|
+
// Reporting the press to JavaScript happens afterwards and asynchronously,
|
|
33
|
+
// because by then the decision has been made.
|
|
34
|
+
//
|
|
35
|
+
// ## Why the registry is here
|
|
36
|
+
//
|
|
37
|
+
// Three hosts asking "does this view claim Cmd+Z" is one decision, and a host
|
|
38
|
+
// keeping its own copy would be a fourth place for it to drift. The tags are
|
|
39
|
+
// React's, so nothing here knows what a view is.
|
|
40
|
+
|
|
41
|
+
#pragma once
|
|
42
|
+
|
|
43
|
+
#include <react/renderer/core/ReactPrimitives.h>
|
|
44
|
+
|
|
45
|
+
#include <cstdint>
|
|
46
|
+
#include <functional>
|
|
47
|
+
#include <optional>
|
|
48
|
+
#include <string>
|
|
49
|
+
#include <vector>
|
|
50
|
+
|
|
51
|
+
namespace basalt {
|
|
52
|
+
|
|
53
|
+
// The names that are not a character. A host maps its own key codes onto these;
|
|
54
|
+
// what it must not do is spell them itself.
|
|
55
|
+
inline constexpr const char *kKeyArrowUp = "ArrowUp";
|
|
56
|
+
inline constexpr const char *kKeyArrowDown = "ArrowDown";
|
|
57
|
+
inline constexpr const char *kKeyArrowLeft = "ArrowLeft";
|
|
58
|
+
inline constexpr const char *kKeyArrowRight = "ArrowRight";
|
|
59
|
+
inline constexpr const char *kKeyEnter = "Enter";
|
|
60
|
+
inline constexpr const char *kKeyTab = "Tab";
|
|
61
|
+
inline constexpr const char *kKeyEscape = "Escape";
|
|
62
|
+
inline constexpr const char *kKeyBackspace = "Backspace";
|
|
63
|
+
inline constexpr const char *kKeyDelete = "Delete";
|
|
64
|
+
inline constexpr const char *kKeyHome = "Home";
|
|
65
|
+
inline constexpr const char *kKeyEnd = "End";
|
|
66
|
+
inline constexpr const char *kKeyPageUp = "PageUp";
|
|
67
|
+
inline constexpr const char *kKeyPageDown = "PageDown";
|
|
68
|
+
// A space is a character like any other, and is named `" "` rather than
|
|
69
|
+
// `"Space"`. Worth a constant because it is the one that looks like a mistake.
|
|
70
|
+
inline constexpr const char *kKeySpace = " ";
|
|
71
|
+
|
|
72
|
+
struct KeyModifiers {
|
|
73
|
+
bool alt = false;
|
|
74
|
+
bool ctrl = false;
|
|
75
|
+
bool meta = false;
|
|
76
|
+
bool shift = false;
|
|
77
|
+
|
|
78
|
+
bool operator==(const KeyModifiers &other) const {
|
|
79
|
+
return alt == other.alt && ctrl == other.ctrl && meta == other.meta &&
|
|
80
|
+
shift == other.shift;
|
|
81
|
+
}
|
|
82
|
+
bool operator!=(const KeyModifiers &other) const { return !(*this == other); }
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// A press, and also a claim: the two are compared for equality, so they are one
|
|
86
|
+
// shape rather than two that have to agree.
|
|
87
|
+
struct KeyCombination {
|
|
88
|
+
std::string key;
|
|
89
|
+
KeyModifiers modifiers;
|
|
90
|
+
|
|
91
|
+
bool operator==(const KeyCombination &other) const {
|
|
92
|
+
return key == other.key && modifiers == other.modifiers;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// What a view claims. Replaces whatever it claimed before rather than adding to
|
|
97
|
+
// it: a re-render produces the whole list, and merging would leave a shortcut
|
|
98
|
+
// live after the app stopped declaring it.
|
|
99
|
+
void setHandledKeys(facebook::react::Tag tag, std::vector<KeyCombination> combinations);
|
|
100
|
+
|
|
101
|
+
// On unmount. Not on every mutation -- an empty list is a view that claims
|
|
102
|
+
// nothing and still exists.
|
|
103
|
+
void clearHandledKeys(facebook::react::Tag tag);
|
|
104
|
+
|
|
105
|
+
// Whether that one view claims it.
|
|
106
|
+
bool handlesKey(facebook::react::Tag tag, const KeyCombination &pressed);
|
|
107
|
+
|
|
108
|
+
// The nearest view in `path` that claims it, or nothing. `path` runs from the
|
|
109
|
+
// focused view outwards to the root, so the first match is the innermost -- the
|
|
110
|
+
// same walk `DragAndDrop`'s drop targets use, and the same reason: a shortcut on
|
|
111
|
+
// a pane should win over the same shortcut on the window.
|
|
112
|
+
std::optional<facebook::react::Tag> handledBy(
|
|
113
|
+
const std::vector<facebook::react::Tag> &path, const KeyCombination &pressed);
|
|
114
|
+
|
|
115
|
+
// Any view that claims it, focus ignored. For the case where nothing has focus
|
|
116
|
+
// at all, which is the ordinary state of a window whose app has declared
|
|
117
|
+
// window-level shortcuts and focused nothing.
|
|
118
|
+
//
|
|
119
|
+
// **Only when nothing is focused.** A host with a focused view must use
|
|
120
|
+
// `handledBy`, because a claim outside the focus path is exactly how a background
|
|
121
|
+
// pane would steal a keystroke from a text field. With nothing focused there is no
|
|
122
|
+
// text field taking keys and no path to be outside of, so the choice is between a
|
|
123
|
+
// window-level shortcut working and it never firing -- and never firing is what a
|
|
124
|
+
// `<KeyHandler>` below the root got before this existed, which is every app that
|
|
125
|
+
// declares shortcuts once at the top.
|
|
126
|
+
//
|
|
127
|
+
// Ambiguous by construction when two views claim the same combination and neither
|
|
128
|
+
// is focused. The first found answers, and which that is depends on the registry's
|
|
129
|
+
// order -- so an app with two window-level handlers for one combination gets one
|
|
130
|
+
// of them and no promise which. Worth knowing rather than worth preventing: the
|
|
131
|
+
// alternative is refusing to fire at all.
|
|
132
|
+
std::optional<facebook::react::Tag> handledByAny(const KeyCombination &pressed);
|
|
133
|
+
|
|
134
|
+
// How many views currently claim anything. For tests, and for a host that wants
|
|
135
|
+
// to skip the walk entirely when nothing is listening.
|
|
136
|
+
std::size_t handledKeyViewCount();
|
|
137
|
+
|
|
138
|
+
// Reporting, after the host has already decided. Set by whatever carries events
|
|
139
|
+
// to JavaScript; absent until then, in which case a press is dropped rather than
|
|
140
|
+
// queued -- a shortcut that fires late is worse than one that did not fire.
|
|
141
|
+
void setKeyListener(std::function<void(facebook::react::Tag, const KeyCombination &)> listener);
|
|
142
|
+
void reportKey(facebook::react::Tag tag, const KeyCombination &pressed);
|
|
143
|
+
|
|
144
|
+
} // namespace basalt
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#include "LogBoxSurface.h"
|
|
2
|
+
|
|
3
|
+
#include "PlatformServices.h"
|
|
4
|
+
|
|
5
|
+
namespace basalt {
|
|
6
|
+
|
|
7
|
+
LogBoxSurfaceDelegate::LogBoxSurfaceDelegate(Show show, Hide hide)
|
|
8
|
+
: show_(std::move(show)), hide_(std::move(hide)) {}
|
|
9
|
+
|
|
10
|
+
void LogBoxSurfaceDelegate::createContentView(std::string appKey) {
|
|
11
|
+
// Only recorded. The module constructs on the JavaScript thread and calls
|
|
12
|
+
// this at once, long before anything wants a surface on screen, and starting
|
|
13
|
+
// one here would mean touching widgets from the wrong thread for no gain.
|
|
14
|
+
appKey_ = std::move(appKey);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
bool LogBoxSurfaceDelegate::isContentViewReady() {
|
|
18
|
+
return !appKey_.empty();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
void LogBoxSurfaceDelegate::destroyContentView() {
|
|
22
|
+
hide();
|
|
23
|
+
appKey_.clear();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
void LogBoxSurfaceDelegate::show() {
|
|
27
|
+
if (showing_ || appKey_.empty() || !show_) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
showing_ = true;
|
|
31
|
+
postToUiThread([show = show_, appKey = appKey_]() { show(appKey); });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
void LogBoxSurfaceDelegate::hide() {
|
|
35
|
+
if (!showing_ || !hide_) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
showing_ = false;
|
|
39
|
+
postToUiThread([hide = hide_]() { hide(); });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
bool LogBoxSurfaceDelegate::isShowing() {
|
|
43
|
+
return showing_;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
} // namespace basalt
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// React Native's error inspector, which is a second surface.
|
|
2
|
+
//
|
|
3
|
+
// The red box is not a native window that a host draws. It is React Native's
|
|
4
|
+
// own JavaScript -- `LogBoxInspectorContainer` -- registered by AppRegistry
|
|
5
|
+
// under the name `"LogBox"`, exactly as an app registers its own component. So
|
|
6
|
+
// a host does not implement an error overlay; it starts a second surface for a
|
|
7
|
+
// component it did not write, on top of the first.
|
|
8
|
+
//
|
|
9
|
+
// That is easy to miss, and missing it is why the inspector did not appear on
|
|
10
|
+
// this platform while the *toasts* did: the notification strip is rendered
|
|
11
|
+
// inside the app's own surface by `AppContainer`, so it has worked since
|
|
12
|
+
// `<View>` and `<Text>` did. Only the full-screen inspector needs a second
|
|
13
|
+
// surface, and the only thing that asks for one is the `LogBox` TurboModule --
|
|
14
|
+
// which ReactCxxPlatform already implements, and only provides when a host
|
|
15
|
+
// hands `ReactHost` a `SurfaceDelegate`. Passing null, as this project did,
|
|
16
|
+
// leaves `NativeLogBox.show()` a call into nothing.
|
|
17
|
+
//
|
|
18
|
+
// ## What is portable about a surface delegate
|
|
19
|
+
//
|
|
20
|
+
// Everything except making a root and starting a surface. The interface has six
|
|
21
|
+
// methods and four of them are bookkeeping; the two that are not are called
|
|
22
|
+
// from the JavaScript thread and have to reach the thread that owns widgets.
|
|
23
|
+
// Getting that wrong is a mounting-manager assertion on a good day. So the
|
|
24
|
+
// bookkeeping and the thread hop are here, once, and a host supplies two
|
|
25
|
+
// functions.
|
|
26
|
+
|
|
27
|
+
#pragma once
|
|
28
|
+
|
|
29
|
+
#include <react/renderer/scheduler/SurfaceDelegate.h>
|
|
30
|
+
|
|
31
|
+
#include <functional>
|
|
32
|
+
#include <string>
|
|
33
|
+
|
|
34
|
+
namespace basalt {
|
|
35
|
+
|
|
36
|
+
class LogBoxSurfaceDelegate final : public facebook::react::SurfaceDelegate {
|
|
37
|
+
public:
|
|
38
|
+
// `show` is handed the app key React Native registered the inspector under,
|
|
39
|
+
// which is always "LogBox" but is passed rather than assumed -- it is the
|
|
40
|
+
// module that decides, and a host should not have to know the string.
|
|
41
|
+
using Show = std::function<void(const std::string &appKey)>;
|
|
42
|
+
using Hide = std::function<void()>;
|
|
43
|
+
|
|
44
|
+
LogBoxSurfaceDelegate(Show show, Hide hide);
|
|
45
|
+
|
|
46
|
+
// --- SurfaceDelegate ------------------------------------------------------
|
|
47
|
+
//
|
|
48
|
+
// Called from the JavaScript thread: `createContentView` from the module's
|
|
49
|
+
// constructor, `show` and `hide` from LogBoxData's setTimeout. So the two
|
|
50
|
+
// that touch a surface hop to the UI thread and the rest only record.
|
|
51
|
+
void createContentView(std::string appKey) override;
|
|
52
|
+
bool isContentViewReady() override;
|
|
53
|
+
void destroyContentView() override;
|
|
54
|
+
void show() override;
|
|
55
|
+
void hide() override;
|
|
56
|
+
bool isShowing() override;
|
|
57
|
+
|
|
58
|
+
private:
|
|
59
|
+
Show show_;
|
|
60
|
+
Hide hide_;
|
|
61
|
+
std::string appKey_;
|
|
62
|
+
// Written on the JavaScript thread and read there too -- every caller of this
|
|
63
|
+
// interface is that thread -- so this needs no lock. The UI thread sees only
|
|
64
|
+
// the two callbacks, and never this.
|
|
65
|
+
bool showing_{false};
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
} // namespace basalt
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// An application menu, described once.
|
|
2
|
+
//
|
|
3
|
+
// The menu bar is the one desktop feature where the three platforms disagree
|
|
4
|
+
// about whether it should exist at all, and this file is shaped by that rather
|
|
5
|
+
// than pretending otherwise.
|
|
6
|
+
//
|
|
7
|
+
// macOS Every application has one, it lives in the system menu bar, and it
|
|
8
|
+
// is not optional -- it is also load-bearing. AppKit routes a key
|
|
9
|
+
// equivalent through the main menu before anything else sees it, so
|
|
10
|
+
// an app with no Edit menu has no working Cmd-C: the field editor
|
|
11
|
+
// never receives `copy:`. That was true of this host until this file
|
|
12
|
+
// existed, and it is the reason it exists now.
|
|
13
|
+
//
|
|
14
|
+
// Windows Conventional for some applications and absent from many. It is a
|
|
15
|
+
// bar inside the window, so it costs client area.
|
|
16
|
+
//
|
|
17
|
+
// GNOME Discouraged outright: the HIG has said to use a header bar and a
|
|
18
|
+
// menu button since GNOME 3, and GTK4 removed the widget that made
|
|
19
|
+
// the old arrangement easy.
|
|
20
|
+
//
|
|
21
|
+
// So an app describes a menu here, and each platform decides what to do with
|
|
22
|
+
// the description. What it must never do is quietly nothing, which is why
|
|
23
|
+
// `applicationMenuSupported()` exists: an app can ask, and this project's own
|
|
24
|
+
// documentation can say, rather than an app shipping with a File menu nobody
|
|
25
|
+
// on Linux can see.
|
|
26
|
+
//
|
|
27
|
+
// ## Roles
|
|
28
|
+
//
|
|
29
|
+
// An item with a `role` is one the platform implements itself -- copy, paste,
|
|
30
|
+
// quit, minimise. It never reaches JavaScript, which is not an optimisation: on
|
|
31
|
+
// macOS `copy:` has to travel the responder chain to reach whichever field has
|
|
32
|
+
// focus, and a round trip through a callback would arrive with the chain gone.
|
|
33
|
+
// The role names are Electron's, because that is the vocabulary a desktop
|
|
34
|
+
// developer already has.
|
|
35
|
+
|
|
36
|
+
#pragma once
|
|
37
|
+
|
|
38
|
+
#include <functional>
|
|
39
|
+
#include <string>
|
|
40
|
+
#include <vector>
|
|
41
|
+
|
|
42
|
+
namespace basalt {
|
|
43
|
+
|
|
44
|
+
struct MenuItemModel {
|
|
45
|
+
// What it says. Ignored for a separator, and optional for a role, where the
|
|
46
|
+
// platform's own word for it is better than any this project would choose.
|
|
47
|
+
std::string label;
|
|
48
|
+
|
|
49
|
+
// One of the names in kMenuRoles below, or empty for an item the app
|
|
50
|
+
// implements. An unknown role is dropped rather than shown: a menu item that
|
|
51
|
+
// does nothing is worse than one that is not there.
|
|
52
|
+
std::string role;
|
|
53
|
+
|
|
54
|
+
// "CmdOrCtrl+O", in Electron's spelling. Ignored for a role, which brings the
|
|
55
|
+
// platform's own shortcut with it.
|
|
56
|
+
std::string accelerator;
|
|
57
|
+
|
|
58
|
+
bool enabled{true};
|
|
59
|
+
bool separator{false};
|
|
60
|
+
|
|
61
|
+
// What comes back when the app's own item is chosen. Zero for a role or a
|
|
62
|
+
// separator, and for a submenu's parent.
|
|
63
|
+
int id{0};
|
|
64
|
+
|
|
65
|
+
std::vector<MenuItemModel> submenu;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// The top level of a menu bar: each entry is a menu, and its `submenu` is what
|
|
69
|
+
// drops down. An entry with no submenu is dropped -- no desktop has a bare
|
|
70
|
+
// command in a menu bar.
|
|
71
|
+
using MenuModel = std::vector<MenuItemModel>;
|
|
72
|
+
|
|
73
|
+
// The roles a platform is expected to implement itself, in Electron's spelling.
|
|
74
|
+
// Anything else in `role` is unknown and the item is dropped.
|
|
75
|
+
//
|
|
76
|
+
// Deliberately short. Each one is a thing every desktop already has a standard
|
|
77
|
+
// action and shortcut for; a role that only one platform has would be a role
|
|
78
|
+
// apps cannot use.
|
|
79
|
+
inline constexpr const char *kMenuRoles[] = {
|
|
80
|
+
"about", "quit", "undo", "redo", "cut", "copy",
|
|
81
|
+
"paste", "delete", "selectAll", "minimize", "zoom", "close",
|
|
82
|
+
"togglefullscreen",
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Whether this platform puts an application menu anywhere a person can see it.
|
|
86
|
+
//
|
|
87
|
+
// False is not a failure and not a promise to fix: see the header. An app that
|
|
88
|
+
// asks can put its commands somewhere else; one that does not is no worse off
|
|
89
|
+
// than it was.
|
|
90
|
+
bool applicationMenuSupported();
|
|
91
|
+
|
|
92
|
+
// Installs `menu` as the application menu, replacing whatever was there.
|
|
93
|
+
//
|
|
94
|
+
// `onChosen` is called with the `id` of an app's own item, on the UI thread. A
|
|
95
|
+
// role never arrives there. An empty menu restores the platform's default,
|
|
96
|
+
// which on macOS is the minimum an application needs to be closable.
|
|
97
|
+
void setApplicationMenu(const MenuModel &menu, std::function<void(int id)> onChosen);
|
|
98
|
+
|
|
99
|
+
// The menu that is actually installed, as text, for BASALT_DUMP_MENU.
|
|
100
|
+
//
|
|
101
|
+
// Read back from the platform rather than remembered from the model, which is
|
|
102
|
+
// the whole point: it says the description became a real NSMenu or HMENU, with
|
|
103
|
+
// the shortcuts the platform attached to its roles. Empty on a platform with no
|
|
104
|
+
// menu bar.
|
|
105
|
+
//
|
|
106
|
+
// One line per item, two spaces of indent per level:
|
|
107
|
+
//
|
|
108
|
+
// Edit
|
|
109
|
+
// Copy [cmd+c]
|
|
110
|
+
// Paste [cmd+v]
|
|
111
|
+
// -
|
|
112
|
+
// Nothing doing (disabled)
|
|
113
|
+
//
|
|
114
|
+
// Called on the UI thread.
|
|
115
|
+
std::string describeApplicationMenu();
|
|
116
|
+
|
|
117
|
+
} // namespace basalt
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#include "MenuModule.h"
|
|
2
|
+
|
|
3
|
+
#include "MenuModel.h"
|
|
4
|
+
#include "PlatformServices.h"
|
|
5
|
+
#include "TestDialog.h"
|
|
6
|
+
|
|
7
|
+
#include <react/bridging/Bridging.h>
|
|
8
|
+
#include <react/bridging/Promise.h>
|
|
9
|
+
|
|
10
|
+
#include <folly/dynamic.h>
|
|
11
|
+
|
|
12
|
+
#include <memory>
|
|
13
|
+
#include <string>
|
|
14
|
+
#include <utility>
|
|
15
|
+
#include <vector>
|
|
16
|
+
|
|
17
|
+
namespace basalt {
|
|
18
|
+
|
|
19
|
+
namespace {
|
|
20
|
+
|
|
21
|
+
using facebook::jsi::Array;
|
|
22
|
+
using facebook::jsi::Object;
|
|
23
|
+
using facebook::jsi::Runtime;
|
|
24
|
+
using facebook::jsi::Value;
|
|
25
|
+
using facebook::react::TurboModule;
|
|
26
|
+
|
|
27
|
+
std::string stringProperty(Runtime &runtime, const Object &item, const char *name) {
|
|
28
|
+
const Value value = item.getProperty(runtime, name);
|
|
29
|
+
return value.isString() ? value.asString(runtime).utf8(runtime) : std::string{};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
bool boolProperty(Runtime &runtime, const Object &item, const char *name, bool fallback) {
|
|
33
|
+
const Value value = item.getProperty(runtime, name);
|
|
34
|
+
return value.isBool() ? value.asBool() : fallback;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// A context menu's entries, which are a flatter thing than the application
|
|
38
|
+
// menu's: one level, no roles, no submenus. That is not a simplification -- a
|
|
39
|
+
// popup menu on all three desktops is a list, and the nesting an application
|
|
40
|
+
// menu has is what a menu *bar* is for.
|
|
41
|
+
std::vector<MenuEntry> entriesFrom(Runtime &runtime, const Value &value) {
|
|
42
|
+
std::vector<MenuEntry> entries;
|
|
43
|
+
if (!value.isObject() || !value.asObject(runtime).isArray(runtime)) {
|
|
44
|
+
return entries;
|
|
45
|
+
}
|
|
46
|
+
const Array array = value.asObject(runtime).asArray(runtime);
|
|
47
|
+
const size_t length = array.size(runtime);
|
|
48
|
+
for (size_t i = 0; i < length; i++) {
|
|
49
|
+
const Value element = array.getValueAtIndex(runtime, i);
|
|
50
|
+
if (!element.isObject()) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const Object item = element.asObject(runtime);
|
|
54
|
+
// `{separator: true}`, which reads better in JavaScript than the empty
|
|
55
|
+
// label the platforms use. An item with no label is one too, so an app that
|
|
56
|
+
// built its list by filtering does not have to notice.
|
|
57
|
+
if (boolProperty(runtime, item, "separator", false)) {
|
|
58
|
+
entries.push_back(MenuEntry::separator());
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
MenuEntry entry;
|
|
62
|
+
entry.label = stringProperty(runtime, item, "label");
|
|
63
|
+
entry.enabled = boolProperty(runtime, item, "enabled", true);
|
|
64
|
+
entry.shortcut = stringProperty(runtime, item, "shortcut");
|
|
65
|
+
entries.push_back(std::move(entry));
|
|
66
|
+
}
|
|
67
|
+
return entries;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
std::vector<MenuItemModel> itemsFrom(Runtime &runtime, const Array &array);
|
|
71
|
+
|
|
72
|
+
MenuItemModel itemFrom(Runtime &runtime, const Object &item) {
|
|
73
|
+
MenuItemModel model;
|
|
74
|
+
model.label = stringProperty(runtime, item, "label");
|
|
75
|
+
model.role = stringProperty(runtime, item, "role");
|
|
76
|
+
model.accelerator = stringProperty(runtime, item, "accelerator");
|
|
77
|
+
|
|
78
|
+
const Value separator = item.getProperty(runtime, "separator");
|
|
79
|
+
model.separator = separator.isBool() && separator.asBool();
|
|
80
|
+
|
|
81
|
+
const Value enabled = item.getProperty(runtime, "enabled");
|
|
82
|
+
// Absent means enabled: an app listing an item without saying meant to show
|
|
83
|
+
// it, and defaulting the other way would grey out every menu ever written.
|
|
84
|
+
model.enabled = !enabled.isBool() || enabled.asBool();
|
|
85
|
+
|
|
86
|
+
const Value id = item.getProperty(runtime, "id");
|
|
87
|
+
model.id = id.isNumber() ? static_cast<int>(id.asNumber()) : 0;
|
|
88
|
+
|
|
89
|
+
const Value submenu = item.getProperty(runtime, "submenu");
|
|
90
|
+
if (submenu.isObject() && submenu.asObject(runtime).isArray(runtime)) {
|
|
91
|
+
model.submenu = itemsFrom(runtime, submenu.asObject(runtime).asArray(runtime));
|
|
92
|
+
}
|
|
93
|
+
return model;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
std::vector<MenuItemModel> itemsFrom(Runtime &runtime, const Array &array) {
|
|
97
|
+
std::vector<MenuItemModel> items;
|
|
98
|
+
const size_t length = array.size(runtime);
|
|
99
|
+
items.reserve(length);
|
|
100
|
+
for (size_t i = 0; i < length; i++) {
|
|
101
|
+
const Value entry = array.getValueAtIndex(runtime, i);
|
|
102
|
+
if (entry.isObject()) {
|
|
103
|
+
items.push_back(itemFrom(runtime, entry.asObject(runtime)));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return items;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
} // namespace
|
|
110
|
+
|
|
111
|
+
DesktopMenuModule::DesktopMenuModule(std::shared_ptr<facebook::react::CallInvoker> jsInvoker)
|
|
112
|
+
: TurboModule(kModuleName, std::move(jsInvoker)) {
|
|
113
|
+
methodMap_["setApplicationMenu"] = MethodMetadata{1, setMenu};
|
|
114
|
+
methodMap_["isSupported"] = MethodMetadata{0, isSupported};
|
|
115
|
+
methodMap_["showContextMenu"] = MethodMetadata{3, showContextMenu};
|
|
116
|
+
// What a NativeEventEmitter over this module calls; the events go out as
|
|
117
|
+
// device events either way.
|
|
118
|
+
methodMap_["addListener"] = MethodMetadata{1, noop};
|
|
119
|
+
methodMap_["removeListeners"] = MethodMetadata{1, noop};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
DesktopMenuModule::~DesktopMenuModule() {
|
|
123
|
+
// The handler holds a pointer to this module's emitter. Cleared on the way
|
|
124
|
+
// out, for the same reason the title bar clears its metrics listener.
|
|
125
|
+
setApplicationMenu(MenuModel{}, nullptr);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
Value DesktopMenuModule::setMenu(Runtime &runtime,
|
|
129
|
+
TurboModule &module,
|
|
130
|
+
const Value *args,
|
|
131
|
+
size_t count) {
|
|
132
|
+
MenuModel model;
|
|
133
|
+
if (count >= 1 && args[0].isObject() && args[0].asObject(runtime).isArray(runtime)) {
|
|
134
|
+
model = itemsFrom(runtime, args[0].asObject(runtime).asArray(runtime));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
auto *self = static_cast<DesktopMenuModule *>(&module);
|
|
138
|
+
// Installed on the UI thread, like everything else that touches a window:
|
|
139
|
+
// this arrives on the JavaScript thread and an NSMenu or an HMENU may not be
|
|
140
|
+
// built there.
|
|
141
|
+
postToUiThread([self, model = std::move(model)] {
|
|
142
|
+
setApplicationMenu(model, [self](int id) {
|
|
143
|
+
self->emitDeviceEvent(kMenuChosenEvent,
|
|
144
|
+
[id](Runtime & /*runtime*/, std::vector<Value> &args) {
|
|
145
|
+
args.emplace_back(Value(id));
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
return Value::undefined();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
Value DesktopMenuModule::isSupported(Runtime & /*runtime*/,
|
|
153
|
+
TurboModule & /*module*/,
|
|
154
|
+
const Value * /*args*/,
|
|
155
|
+
size_t /*count*/) {
|
|
156
|
+
return Value(applicationMenuSupported());
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
Value DesktopMenuModule::noop(Runtime & /*runtime*/,
|
|
160
|
+
TurboModule & /*module*/,
|
|
161
|
+
const Value * /*args*/,
|
|
162
|
+
size_t /*count*/) {
|
|
163
|
+
return Value::undefined();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
Value DesktopMenuModule::showContextMenu(Runtime &runtime,
|
|
167
|
+
TurboModule &module,
|
|
168
|
+
const Value *args,
|
|
169
|
+
size_t count) {
|
|
170
|
+
MenuRequest request;
|
|
171
|
+
if (count >= 1) {
|
|
172
|
+
request.entries = entriesFrom(runtime, args[0]);
|
|
173
|
+
}
|
|
174
|
+
// Negative is "wherever the pointer is", which is what the seam already means
|
|
175
|
+
// and what a menu opened from the keyboard wants. An app that passes nothing
|
|
176
|
+
// gets that rather than the top-left corner.
|
|
177
|
+
if (count >= 3 && args[1].isNumber() && args[2].isNumber()) {
|
|
178
|
+
request.x = args[1].asNumber();
|
|
179
|
+
request.y = args[2].asNumber();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
auto jsInvoker = static_cast<DesktopMenuModule &>(module).jsInvoker_;
|
|
183
|
+
auto promise =
|
|
184
|
+
std::make_shared<facebook::react::AsyncPromise<folly::dynamic>>(runtime, jsInvoker);
|
|
185
|
+
|
|
186
|
+
if (request.entries.empty()) {
|
|
187
|
+
// Nothing to show. Resolved rather than rejected: an app whose menu came
|
|
188
|
+
// out empty because everything was filtered away has not made a mistake,
|
|
189
|
+
// and a rejection would make it handle one.
|
|
190
|
+
promise->resolve(folly::dynamic(nullptr));
|
|
191
|
+
return Value(runtime, facebook::react::bridging::toJs(runtime, *promise, jsInvoker));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// `presentMenu`, not `showMenu`: everything that would put a popup on screen
|
|
195
|
+
// goes through the scripted answer, and here it is not merely tidiness. On
|
|
196
|
+
// macOS `popUpMenuPositioningItem` runs the menu's own tracking loop on the
|
|
197
|
+
// main thread, so a menu nobody dismisses stops an automated run where it
|
|
198
|
+
// stands. See core/TestDialog.h.
|
|
199
|
+
presentMenu(request, [promise](int index) {
|
|
200
|
+
// Null for dismissed, which is the same shape the file dialogs answer a
|
|
201
|
+
// cancel with: an index is a number, and a caller checking `if (index)`
|
|
202
|
+
// would read entry zero as nothing.
|
|
203
|
+
promise->resolve(index < 0 ? folly::dynamic(nullptr) : folly::dynamic(index));
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
return Value(runtime, facebook::react::bridging::toJs(runtime, *promise, jsInvoker));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
} // namespace basalt
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// `BasaltMenu`: the application menu, from JavaScript.
|
|
2
|
+
//
|
|
3
|
+
// The application menu: `<Menu>` describes one, this installs it, and an item
|
|
4
|
+
// the app owns comes back as a device event carrying its id -- which is the
|
|
5
|
+
// same arrangement the title bar and the window use, for the same reason: a
|
|
6
|
+
// menu item is chosen at a time nothing is waiting for it.
|
|
7
|
+
//
|
|
8
|
+
// And context menus, which are the other kind and answer differently. See
|
|
9
|
+
// `showContextMenu` below.
|
|
10
|
+
//
|
|
11
|
+
// Roles never come back. They are handled where they are: see
|
|
12
|
+
// core/MenuModel.h, and appkit/AppKitMenuBar.mm for the part that makes Cmd-C
|
|
13
|
+
// reach a text field.
|
|
14
|
+
|
|
15
|
+
#pragma once
|
|
16
|
+
|
|
17
|
+
#include <ReactCommon/TurboModule.h>
|
|
18
|
+
|
|
19
|
+
#include <memory>
|
|
20
|
+
|
|
21
|
+
namespace basalt {
|
|
22
|
+
|
|
23
|
+
class DesktopMenuModule : public facebook::react::TurboModule {
|
|
24
|
+
public:
|
|
25
|
+
static constexpr const char *kModuleName = "BasaltMenu";
|
|
26
|
+
|
|
27
|
+
explicit DesktopMenuModule(std::shared_ptr<facebook::react::CallInvoker> jsInvoker);
|
|
28
|
+
~DesktopMenuModule() override;
|
|
29
|
+
|
|
30
|
+
private:
|
|
31
|
+
// `setApplicationMenu(items)`, where items are the shape core/MenuModel.h
|
|
32
|
+
// describes. An empty list restores the platform's default.
|
|
33
|
+
static facebook::jsi::Value setMenu(facebook::jsi::Runtime &runtime,
|
|
34
|
+
facebook::react::TurboModule &module,
|
|
35
|
+
const facebook::jsi::Value *args,
|
|
36
|
+
size_t count);
|
|
37
|
+
// `isSupported()`: whether this platform puts a menu bar anywhere a person
|
|
38
|
+
// can see it. False on GNOME, and not a promise to change; see
|
|
39
|
+
// core/MenuModel.h.
|
|
40
|
+
static facebook::jsi::Value isSupported(facebook::jsi::Runtime &runtime,
|
|
41
|
+
facebook::react::TurboModule &module,
|
|
42
|
+
const facebook::jsi::Value *args,
|
|
43
|
+
size_t count);
|
|
44
|
+
// `showContextMenu(items, x, y)` -> a promise of the index chosen, or null
|
|
45
|
+
// when it was dismissed.
|
|
46
|
+
//
|
|
47
|
+
// A promise rather than the device event the application menu uses, and the
|
|
48
|
+
// difference is worth saying. A menu bar is installed once and its items are
|
|
49
|
+
// chosen at times nothing is waiting for; a context menu is opened by an app
|
|
50
|
+
// that is, right then, asking a question -- so the answer belongs to the call
|
|
51
|
+
// that asked it rather than to a listener somewhere else.
|
|
52
|
+
static facebook::jsi::Value showContextMenu(facebook::jsi::Runtime &runtime,
|
|
53
|
+
facebook::react::TurboModule &module,
|
|
54
|
+
const facebook::jsi::Value *args,
|
|
55
|
+
size_t count);
|
|
56
|
+
static facebook::jsi::Value noop(facebook::jsi::Runtime &runtime,
|
|
57
|
+
facebook::react::TurboModule &module,
|
|
58
|
+
const facebook::jsi::Value *args,
|
|
59
|
+
size_t count);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// The device event an item's id arrives on. Named here so the two sides cannot
|
|
63
|
+
// drift.
|
|
64
|
+
inline constexpr const char *kMenuChosenEvent = "basaltMenuItemChosen";
|
|
65
|
+
|
|
66
|
+
} // namespace basalt
|