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,203 @@
|
|
|
1
|
+
#include "ReanimatedModule.h"
|
|
2
|
+
|
|
3
|
+
#include "Gestures.h"
|
|
4
|
+
#include "PlatformServices.h"
|
|
5
|
+
#include "UIManagerAccess.h"
|
|
6
|
+
|
|
7
|
+
#include <glog/logging.h>
|
|
8
|
+
|
|
9
|
+
#ifdef BASALT_HAS_REANIMATED
|
|
10
|
+
#include <reanimated/NativeModules/ReanimatedModuleProxy.h>
|
|
11
|
+
#include <reanimated/RuntimeDecorators/RNRuntimeDecorator.h>
|
|
12
|
+
#include <reanimated/Tools/PlatformDepMethodsHolder.h>
|
|
13
|
+
#include <worklets/Compat/StableApi.h>
|
|
14
|
+
#endif
|
|
15
|
+
|
|
16
|
+
namespace basalt {
|
|
17
|
+
|
|
18
|
+
bool hasReanimated() {
|
|
19
|
+
#ifdef BASALT_HAS_REANIMATED
|
|
20
|
+
return true;
|
|
21
|
+
#else
|
|
22
|
+
return false;
|
|
23
|
+
#endif
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
#ifdef BASALT_HAS_REANIMATED
|
|
27
|
+
|
|
28
|
+
namespace {
|
|
29
|
+
|
|
30
|
+
using facebook::jsi::Runtime;
|
|
31
|
+
using facebook::jsi::Value;
|
|
32
|
+
using facebook::react::TurboModule;
|
|
33
|
+
|
|
34
|
+
std::shared_ptr<reanimated::ReanimatedModuleProxy> &heldProxy() {
|
|
35
|
+
static std::shared_ptr<reanimated::ReanimatedModuleProxy> proxy;
|
|
36
|
+
return proxy;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Reanimated's operations are flushed once a frame, forever, from the moment it
|
|
40
|
+
// is installed. The frame is a timer for the same reason worklets' is: the
|
|
41
|
+
// display link on each platform belongs to React Native's own choreographer,
|
|
42
|
+
// which pauses whenever React Native has no animation of its own -- which is
|
|
43
|
+
// exactly when a Reanimated one might be running.
|
|
44
|
+
constexpr double kFrameMilliseconds = 16;
|
|
45
|
+
|
|
46
|
+
void scheduleFlush() {
|
|
47
|
+
postDelayed(kFrameMilliseconds, []() {
|
|
48
|
+
if (auto proxy = heldProxy()) {
|
|
49
|
+
proxy->performOperations();
|
|
50
|
+
scheduleFlush();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
reanimated::PlatformDepMethodsHolder makePlatformDepMethodsHolder() {
|
|
56
|
+
reanimated::PlatformDepMethodsHolder holder{};
|
|
57
|
+
|
|
58
|
+
holder.requestRender = [](std::function<void(const double)> callback) {
|
|
59
|
+
postDelayed(kFrameMilliseconds, [callback = std::move(callback)]() {
|
|
60
|
+
callback(monotonicMilliseconds());
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
#ifdef __APPLE__
|
|
65
|
+
// iOS takes a snapshot of a view before a shared-element transition. Nothing
|
|
66
|
+
// here does shared-element transitions, and the field exists on Apple builds
|
|
67
|
+
// whether or not it is used.
|
|
68
|
+
holder.forceScreenSnapshotFunction = [](facebook::react::Tag) {};
|
|
69
|
+
#endif
|
|
70
|
+
|
|
71
|
+
// The direct-to-view path, which skips the shadow tree for props that can be
|
|
72
|
+
// applied to a mounted view immediately. Not wired: this platform's mounting
|
|
73
|
+
// managers only accept mutations from a Fabric transaction, which is what
|
|
74
|
+
// makes their thread rule assertable. Reanimated's other path -- committing
|
|
75
|
+
// to the shadow tree -- is the one used here, and it is the one that works on
|
|
76
|
+
// every platform.
|
|
77
|
+
holder.synchronouslyUpdateUIPropsFunction = [](const int, const folly::dynamic &) {};
|
|
78
|
+
|
|
79
|
+
holder.getAnimationTimestamp = []() { return monotonicMilliseconds(); };
|
|
80
|
+
|
|
81
|
+
// useAnimatedSensor. A desktop has no accelerometer, gyroscope or
|
|
82
|
+
// magnetometer; -1 is Reanimated's own "could not register".
|
|
83
|
+
holder.registerSensor = [](int, int, int, std::function<void(double[], int)>) { return -1; };
|
|
84
|
+
holder.unregisterSensor = [](int) {};
|
|
85
|
+
|
|
86
|
+
// A worklet telling a gesture handler what to do, which is how
|
|
87
|
+
// GestureStateManager.activate() and friends work. The registry is
|
|
88
|
+
// core/Gestures.h's, so this is the two halves of phase 37 and phase 38
|
|
89
|
+
// meeting.
|
|
90
|
+
holder.setGestureStateFunction = [](int handlerTag, int newState) {
|
|
91
|
+
postToUiThread([handlerTag, newState]() { gestures().setStateFromWorklet(handlerTag, newState); });
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// A desktop has no soft keyboard to move out of the way of.
|
|
95
|
+
holder.subscribeForKeyboardEvents = [](std::function<void(int, int)>, bool, bool) { return 0; };
|
|
96
|
+
holder.unsubscribeFromKeyboardEvents = [](int) {};
|
|
97
|
+
|
|
98
|
+
holder.maybeFlushUIUpdatesQueueFunction = []() {};
|
|
99
|
+
|
|
100
|
+
// CSS transitions driven by the platform rather than by Reanimated's own
|
|
101
|
+
// clock -- an optimisation Apple has and this does not. Declining every
|
|
102
|
+
// property means Reanimated animates them itself.
|
|
103
|
+
holder.cssCanRouteProperty = [](const std::string &,
|
|
104
|
+
const reanimated::css::EasingConfig &) { return false; };
|
|
105
|
+
holder.cssRemoveTransition = [](facebook::react::Tag, const std::string &) {};
|
|
106
|
+
|
|
107
|
+
// Hover and press pseudo-selectors, which need the view layer to report
|
|
108
|
+
// pointer state per view. Not wired yet.
|
|
109
|
+
holder.attachPseudoSelector = [](facebook::react::Tag,
|
|
110
|
+
reanimated::PseudoSelector,
|
|
111
|
+
std::function<void(bool)>) {};
|
|
112
|
+
holder.detachPseudoSelector = [](facebook::react::Tag, reanimated::PseudoSelector) {};
|
|
113
|
+
|
|
114
|
+
return holder;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
} // namespace
|
|
118
|
+
|
|
119
|
+
DesktopReanimatedModule::DesktopReanimatedModule(
|
|
120
|
+
std::shared_ptr<facebook::react::CallInvoker> jsInvoker)
|
|
121
|
+
: TurboModule(kModuleName, std::move(jsInvoker)) {
|
|
122
|
+
methodMap_["installTurboModule"] = MethodMetadata{0, installTurboModule};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
DesktopReanimatedModule::~DesktopReanimatedModule() {
|
|
126
|
+
heldProxy().reset();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
Value DesktopReanimatedModule::installTurboModule(Runtime &runtime,
|
|
130
|
+
TurboModule &module,
|
|
131
|
+
const Value * /*args*/,
|
|
132
|
+
size_t /*count*/) {
|
|
133
|
+
auto &self = static_cast<DesktopReanimatedModule &>(module);
|
|
134
|
+
|
|
135
|
+
// Worklets put both of these on the JavaScript global when its own module
|
|
136
|
+
// installed. Reading them back is Reanimated's own arrangement for finding
|
|
137
|
+
// the runtime it shares with worklets rather than making a second one.
|
|
138
|
+
auto global = runtime.global();
|
|
139
|
+
auto uiWorkletRuntime = worklets::getWorkletRuntimeFromHolder(
|
|
140
|
+
runtime, global.getPropertyAsObject(runtime, "__UI_WORKLET_RUNTIME_HOLDER"));
|
|
141
|
+
auto uiScheduler = worklets::getUISchedulerFromHolder(
|
|
142
|
+
runtime, global.getPropertyAsObject(runtime, "__UI_SCHEDULER_HOLDER"));
|
|
143
|
+
|
|
144
|
+
if (uiWorkletRuntime == nullptr || uiScheduler == nullptr) {
|
|
145
|
+
LOG(ERROR) << "reanimated: the worklets runtime is not installed; is "
|
|
146
|
+
"react-native-worklets compiled into this host?";
|
|
147
|
+
return Value(false);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const auto platformDepMethodsHolder = makePlatformDepMethodsHolder();
|
|
151
|
+
auto proxy = std::make_shared<reanimated::ReanimatedModuleProxy>(uiWorkletRuntime,
|
|
152
|
+
uiScheduler,
|
|
153
|
+
runtime,
|
|
154
|
+
self.jsInvoker_,
|
|
155
|
+
platformDepMethodsHolder,
|
|
156
|
+
/*isReducedMotion=*/false);
|
|
157
|
+
proxy->init(platformDepMethodsHolder);
|
|
158
|
+
|
|
159
|
+
auto &uiRuntime = worklets::getJSIRuntimeFromWorkletRuntime(uiWorkletRuntime);
|
|
160
|
+
reanimated::RNRuntimeDecorator::decorate(runtime, uiRuntime, proxy);
|
|
161
|
+
|
|
162
|
+
// The shadow-tree half. Without a UIManager Reanimated still runs -- shared
|
|
163
|
+
// values update and callbacks fire -- and nothing it animates reaches the
|
|
164
|
+
// screen, which is the confusing failure, so it is worth a loud line.
|
|
165
|
+
if (auto uiManager = sharedUIManager()) {
|
|
166
|
+
proxy->initializeFabric(uiManager);
|
|
167
|
+
} else {
|
|
168
|
+
LOG(ERROR) << "reanimated: no UIManager, so animated styles will not be applied";
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Every event Fabric dispatches, offered to Reanimated before the components
|
|
172
|
+
// see it. That is how a scroll handler or a gesture callback written as a
|
|
173
|
+
// worklet runs at all: without it they are declared, attached and never
|
|
174
|
+
// called.
|
|
175
|
+
//
|
|
176
|
+
// Only events raised on the UI thread, which is what iOS does and for the
|
|
177
|
+
// same reason -- a worklet runtime may only be touched from the thread that
|
|
178
|
+
// owns it, and an event from the JavaScript thread (a layout, a state update)
|
|
179
|
+
// arrives on the wrong one.
|
|
180
|
+
std::weak_ptr<reanimated::ReanimatedModuleProxy> weakProxy = proxy;
|
|
181
|
+
const bool listening = installEventListener(std::make_shared<facebook::react::EventListener>(
|
|
182
|
+
[weakProxy](const facebook::react::RawEvent &rawEvent) {
|
|
183
|
+
if (!isUiThread()) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
if (auto proxy = weakProxy.lock()) {
|
|
187
|
+
return proxy->handleRawEvent(rawEvent, monotonicMilliseconds());
|
|
188
|
+
}
|
|
189
|
+
return false;
|
|
190
|
+
}));
|
|
191
|
+
if (!listening) {
|
|
192
|
+
LOG(WARNING) << "reanimated: no event listener, so scroll and gesture "
|
|
193
|
+
"worklets will not run";
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
heldProxy() = proxy;
|
|
197
|
+
scheduleFlush();
|
|
198
|
+
return Value(true);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
#endif
|
|
202
|
+
|
|
203
|
+
} // namespace basalt
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// react-native-reanimated, on top of core/WorkletsModule.h.
|
|
2
|
+
//
|
|
3
|
+
// The same arrangement again, and by now the pattern is the point: the package
|
|
4
|
+
// ships a large body of portable C++ -- the animation clock, the shadow-tree
|
|
5
|
+
// cloning that applies an animated style without a React re-render, the CSS
|
|
6
|
+
// engine, the registries -- and a small per-platform half that wires it to a
|
|
7
|
+
// host. This is that half.
|
|
8
|
+
//
|
|
9
|
+
// It is a thicker seam than worklets' because Reanimated reaches further into
|
|
10
|
+
// the renderer. Three things have to be found for it:
|
|
11
|
+
//
|
|
12
|
+
// - The **UI worklet runtime** and its scheduler, which worklets installed on
|
|
13
|
+
// the JavaScript global and which Reanimated reads back from there. That is
|
|
14
|
+
// Reanimated's own arrangement, not something invented here.
|
|
15
|
+
// - The **UIManager**, because Reanimated commits to the shadow tree itself.
|
|
16
|
+
// ReactCxxPlatform hands one to the mounting manager, which is where this
|
|
17
|
+
// gets it; see core/UIManagerAccess.h.
|
|
18
|
+
// - A **frame**, on which its operations are flushed.
|
|
19
|
+
//
|
|
20
|
+
// What is deliberately not here is `PlatformDepMethodsHolder`'s long tail:
|
|
21
|
+
// sensors, keyboard events, screen snapshots and pseudo-selector observers are
|
|
22
|
+
// no-ops, because a desktop has no accelerometer and no soft keyboard, and the
|
|
23
|
+
// other two are iOS's answers to iOS problems. Each one is a field in a struct
|
|
24
|
+
// rather than a silent omission, which is the useful thing about that struct.
|
|
25
|
+
|
|
26
|
+
#pragma once
|
|
27
|
+
|
|
28
|
+
#include <ReactCommon/TurboModule.h>
|
|
29
|
+
|
|
30
|
+
#include <memory>
|
|
31
|
+
|
|
32
|
+
namespace basalt {
|
|
33
|
+
|
|
34
|
+
// Whether this build has react-native-reanimated compiled into it.
|
|
35
|
+
bool hasReanimated();
|
|
36
|
+
|
|
37
|
+
#ifdef BASALT_HAS_REANIMATED
|
|
38
|
+
|
|
39
|
+
class DesktopReanimatedModule : public facebook::react::TurboModule {
|
|
40
|
+
public:
|
|
41
|
+
static constexpr const char *kModuleName = "ReanimatedModule";
|
|
42
|
+
|
|
43
|
+
explicit DesktopReanimatedModule(std::shared_ptr<facebook::react::CallInvoker> jsInvoker);
|
|
44
|
+
~DesktopReanimatedModule() override;
|
|
45
|
+
|
|
46
|
+
private:
|
|
47
|
+
static facebook::jsi::Value installTurboModule(facebook::jsi::Runtime &runtime,
|
|
48
|
+
facebook::react::TurboModule &module,
|
|
49
|
+
const facebook::jsi::Value *args,
|
|
50
|
+
size_t count);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
#endif
|
|
54
|
+
|
|
55
|
+
} // namespace basalt
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#include "ScrollAnimation.h"
|
|
2
|
+
|
|
3
|
+
namespace basalt {
|
|
4
|
+
|
|
5
|
+
namespace {
|
|
6
|
+
|
|
7
|
+
// Cubic ease-in-out on [0, 1]. See the header for why this curve.
|
|
8
|
+
double eased(double t) {
|
|
9
|
+
if (t < 0.5) {
|
|
10
|
+
return 4.0 * t * t * t;
|
|
11
|
+
}
|
|
12
|
+
const double shifted = -2.0 * t + 2.0;
|
|
13
|
+
return 1.0 - (shifted * shifted * shifted) / 2.0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
} // namespace
|
|
17
|
+
|
|
18
|
+
bool ScrollAnimation::start(double fromX, double fromY, double toX, double toY) {
|
|
19
|
+
if (fromX == toX && fromY == toY) {
|
|
20
|
+
running_ = false;
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
running_ = true;
|
|
24
|
+
elapsed_ = 0.0;
|
|
25
|
+
fromX_ = fromX;
|
|
26
|
+
fromY_ = fromY;
|
|
27
|
+
toX_ = toX;
|
|
28
|
+
toY_ = toY;
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
bool ScrollAnimation::advance(double seconds, double &x, double &y) {
|
|
33
|
+
if (!running_) {
|
|
34
|
+
x = toX_;
|
|
35
|
+
y = toY_;
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
elapsed_ += seconds;
|
|
40
|
+
if (elapsed_ >= kDurationSeconds) {
|
|
41
|
+
// Exactly the target, not the curve evaluated near 1.0. An app that scrolls
|
|
42
|
+
// to a computed offset and then reads it back should get its own number.
|
|
43
|
+
x = toX_;
|
|
44
|
+
y = toY_;
|
|
45
|
+
running_ = false;
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const double progress = eased(elapsed_ / kDurationSeconds);
|
|
50
|
+
x = fromX_ + (toX_ - fromX_) * progress;
|
|
51
|
+
y = fromY_ + (toY_ - fromY_) * progress;
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
void ScrollAnimation::stop() {
|
|
56
|
+
running_ = false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
} // namespace basalt
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// A programmatic scroll that moves rather than jumps.
|
|
2
|
+
//
|
|
3
|
+
// `scrollTo({x, y, animated: true})` is one of the most-used things a list does
|
|
4
|
+
// -- scroll to top, scroll to the newest message -- and until now the `animated`
|
|
5
|
+
// flag was parsed and dropped, so every one of them arrived instantly.
|
|
6
|
+
//
|
|
7
|
+
// ## Why this is not ScrollMomentum
|
|
8
|
+
//
|
|
9
|
+
// The file next door is deliberate about it: a fling is velocity with friction
|
|
10
|
+
// and *no target*, because where it lands is a consequence of how hard it was
|
|
11
|
+
// thrown. This is the opposite -- a target is the only thing given, and what has
|
|
12
|
+
// to be invented is the path to it. Exponential friction cannot be used here at
|
|
13
|
+
// all: it approaches its destination asymptotically and never arrives.
|
|
14
|
+
//
|
|
15
|
+
// So: a fixed duration and an easing curve, which is what UIScrollView and
|
|
16
|
+
// Android's smooth scroller both do, and what makes two of these in a row look
|
|
17
|
+
// deliberate rather than elastic.
|
|
18
|
+
//
|
|
19
|
+
// ## The curve
|
|
20
|
+
//
|
|
21
|
+
// Cubic ease-in-out. It starts slow, accelerates, and settles -- and, more
|
|
22
|
+
// usefully, its derivative is zero at both ends, so a scroll that begins where
|
|
23
|
+
// the last one ended has no visible seam. A linear ramp is the obvious
|
|
24
|
+
// alternative and looks mechanical precisely because it starts and stops at full
|
|
25
|
+
// speed.
|
|
26
|
+
//
|
|
27
|
+
// Absolute positions rather than deltas, unlike ScrollMomentum. A fling asks
|
|
28
|
+
// "how far since last frame", because it has no idea where it will end; an
|
|
29
|
+
// animation knows exactly where it must be at time t, and computing that from
|
|
30
|
+
// the start point avoids the drift that accumulating deltas would give over
|
|
31
|
+
// ninety frames.
|
|
32
|
+
|
|
33
|
+
#pragma once
|
|
34
|
+
|
|
35
|
+
namespace basalt {
|
|
36
|
+
|
|
37
|
+
class ScrollAnimation {
|
|
38
|
+
public:
|
|
39
|
+
// Long enough to read as movement, short enough not to feel slow. iOS uses
|
|
40
|
+
// about this for `setContentOffset:animated:`; Android's default smooth
|
|
41
|
+
// scroll is a little longer and feels it.
|
|
42
|
+
static constexpr double kDurationSeconds = 0.3;
|
|
43
|
+
|
|
44
|
+
// Begins a scroll from one offset to another. A target equal to the start is
|
|
45
|
+
// not an animation and returns false, so a caller can apply the offset and be
|
|
46
|
+
// done rather than running a curve that goes nowhere.
|
|
47
|
+
bool start(double fromX, double fromY, double toX, double toY);
|
|
48
|
+
|
|
49
|
+
// Advances by `seconds` and reports where the offset should now be. Returns
|
|
50
|
+
// false on the final step, whose position is exactly the target -- the curve
|
|
51
|
+
// is evaluated at 1.0 rather than approached, so an animated scroll always
|
|
52
|
+
// arrives at the number it was asked for.
|
|
53
|
+
bool advance(double seconds, double &x, double &y);
|
|
54
|
+
|
|
55
|
+
void stop();
|
|
56
|
+
|
|
57
|
+
bool isRunning() const {
|
|
58
|
+
return running_;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private:
|
|
62
|
+
bool running_{false};
|
|
63
|
+
double elapsed_{0.0};
|
|
64
|
+
double fromX_{0.0};
|
|
65
|
+
double fromY_{0.0};
|
|
66
|
+
double toX_{0.0};
|
|
67
|
+
double toY_{0.0};
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
} // namespace basalt
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#include "ScrollBounds.h"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
|
|
5
|
+
namespace basalt {
|
|
6
|
+
|
|
7
|
+
ScrollRange scrollRangeFor(double containerLength,
|
|
8
|
+
double contentLength,
|
|
9
|
+
ScrollAxisInsets insets) {
|
|
10
|
+
ScrollRange range;
|
|
11
|
+
// Pulling *before* the content is what a leading inset buys, so the range
|
|
12
|
+
// starts below zero rather than at it.
|
|
13
|
+
range.minimum = -insets.leading;
|
|
14
|
+
range.maximum = std::max(range.minimum, contentLength - containerLength + insets.trailing);
|
|
15
|
+
return range;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
double clampScrollOffset(double offset,
|
|
19
|
+
double containerLength,
|
|
20
|
+
double contentLength,
|
|
21
|
+
ScrollAxisInsets insets) {
|
|
22
|
+
const ScrollRange range = scrollRangeFor(containerLength, contentLength, insets);
|
|
23
|
+
return std::clamp(offset, range.minimum, range.maximum);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
} // namespace basalt
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// How far a scroll view may be scrolled, once `contentInset` has had its say.
|
|
2
|
+
//
|
|
3
|
+
// Three hosts had the same four lines of arithmetic -- clamp between zero and
|
|
4
|
+
// `content - container` -- and none of them read `contentInset`, which was
|
|
5
|
+
// parsed, stored, echoed back in the scroll event, and otherwise ignored. A
|
|
6
|
+
// list that asked to be inset from the top scrolled as though it had not.
|
|
7
|
+
//
|
|
8
|
+
// ## What an inset does
|
|
9
|
+
//
|
|
10
|
+
// It is not padding. Padding makes the content bigger; an inset makes the
|
|
11
|
+
// *scrollable range* bigger while leaving the content the size it is. With a
|
|
12
|
+
// top inset of 50 the list may be pulled 50 points further down than its first
|
|
13
|
+
// row, exposing empty space above it -- which is what a header that overlays
|
|
14
|
+
// the list needs, and the reason iOS has the prop at all.
|
|
15
|
+
//
|
|
16
|
+
// So the range runs from `-leading` to `content - container + trailing`,
|
|
17
|
+
// instead of from zero to `content - container`. With no insets those are the
|
|
18
|
+
// same numbers, which is why nothing changed for anybody who never set one.
|
|
19
|
+
//
|
|
20
|
+
// ## Two insets, not one
|
|
21
|
+
//
|
|
22
|
+
// `contentInset` moves the content and `scrollIndicatorInsets` moves the
|
|
23
|
+
// scrollbar, and an app may set either without the other: a header that covers
|
|
24
|
+
// the top of the list usually wants both, and a window with a rounded corner
|
|
25
|
+
// wants only the second. They are separate parameters here for the same reason
|
|
26
|
+
// React Native keeps them separate props.
|
|
27
|
+
|
|
28
|
+
#pragma once
|
|
29
|
+
|
|
30
|
+
namespace basalt {
|
|
31
|
+
|
|
32
|
+
// One axis of an `EdgeInsets`: the inset before the content and the one after
|
|
33
|
+
// it. Vertically that is top and bottom; horizontally, left and right.
|
|
34
|
+
struct ScrollAxisInsets {
|
|
35
|
+
double leading{0.0};
|
|
36
|
+
double trailing{0.0};
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// The offsets a scroll view may rest at, on one axis. `minimum` is zero or
|
|
40
|
+
// negative, `maximum` is at least `minimum`.
|
|
41
|
+
struct ScrollRange {
|
|
42
|
+
double minimum{0.0};
|
|
43
|
+
double maximum{0.0};
|
|
44
|
+
|
|
45
|
+
double length() const { return maximum - minimum; }
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// The range for one axis.
|
|
49
|
+
//
|
|
50
|
+
// `maximum` is never below `minimum`: content shorter than its container with a
|
|
51
|
+
// leading inset can still be pulled, and the two ends must not cross, or a
|
|
52
|
+
// clamp between them would answer with whichever was written last.
|
|
53
|
+
ScrollRange scrollRangeFor(double containerLength,
|
|
54
|
+
double contentLength,
|
|
55
|
+
ScrollAxisInsets insets = {});
|
|
56
|
+
|
|
57
|
+
// An offset brought inside that range.
|
|
58
|
+
//
|
|
59
|
+
// The one place the three hosts agreed already, and now the one place it is
|
|
60
|
+
// written: an offset outside the range is a momentum frame, a `scrollTo` past
|
|
61
|
+
// the end, or content that shrank under a scrolled view, and all three want the
|
|
62
|
+
// nearest reachable position rather than a refusal.
|
|
63
|
+
double clampScrollOffset(double offset,
|
|
64
|
+
double containerLength,
|
|
65
|
+
double contentLength,
|
|
66
|
+
ScrollAxisInsets insets = {});
|
|
67
|
+
|
|
68
|
+
} // namespace basalt
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#include "ScrollIndicator.h"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
|
|
5
|
+
namespace basalt {
|
|
6
|
+
|
|
7
|
+
ScrollIndicator scrollIndicatorFor(double containerLength,
|
|
8
|
+
double contentLength,
|
|
9
|
+
double scrollOffset,
|
|
10
|
+
ScrollAxisInsets content,
|
|
11
|
+
ScrollAxisInsets indicator) {
|
|
12
|
+
ScrollIndicator result;
|
|
13
|
+
if (containerLength <= 0.0) {
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Nothing to indicate when there is nowhere to go. Asked of the range rather
|
|
18
|
+
// than of the content, because a `contentInset` is somewhere to go: a list
|
|
19
|
+
// that fits exactly but can be pulled down past its top does scroll, and a
|
|
20
|
+
// bar is how you know that.
|
|
21
|
+
const ScrollRange range = scrollRangeFor(containerLength, contentLength, content);
|
|
22
|
+
if (range.length() <= 0.0) {
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// The track: the view, less the standard inset at each end, less whatever
|
|
27
|
+
// `scrollIndicatorInsets` asks for on top of that.
|
|
28
|
+
const double start = kScrollIndicatorInset + indicator.leading;
|
|
29
|
+
const double end = containerLength - kScrollIndicatorInset - indicator.trailing;
|
|
30
|
+
const double track = end - start;
|
|
31
|
+
if (track <= 0.0) {
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// The thumb is as long as the visible fraction of everything there is to
|
|
36
|
+
// scroll through, with a floor. A very long list would otherwise end up with
|
|
37
|
+
// a one-pixel thumb.
|
|
38
|
+
const double scrollable = range.length() + containerLength;
|
|
39
|
+
const double fraction = containerLength / scrollable;
|
|
40
|
+
const double length = std::min(track, std::max(kScrollIndicatorMinimumLength, track * fraction));
|
|
41
|
+
|
|
42
|
+
const double progress = std::clamp((scrollOffset - range.minimum) / range.length(), 0.0, 1.0);
|
|
43
|
+
|
|
44
|
+
result.visible = true;
|
|
45
|
+
result.length = length;
|
|
46
|
+
result.offset = start + progress * (track - length);
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
} // namespace basalt
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// The scrollbar a scroll view draws over itself.
|
|
2
|
+
//
|
|
3
|
+
// None of the three hosts drew one, so a long list gave no sign of how long it
|
|
4
|
+
// was or where you were in it -- which on a desktop reads as a bug rather than
|
|
5
|
+
// a style.
|
|
6
|
+
//
|
|
7
|
+
// ## Drawn rather than borrowed
|
|
8
|
+
//
|
|
9
|
+
// Each toolkit has a scrollbar and none of them is available here. GTK's comes
|
|
10
|
+
// with `GtkScrolledWindow`, AppKit's `NSScroller` comes with `NSScrollView`,
|
|
11
|
+
// and this platform's scroll view is neither -- it is a `<View>` that moves its
|
|
12
|
+
// children, because that is what Fabric hands it. Adopting a toolkit scroll
|
|
13
|
+
// container would mean giving it the scrolling too, and then React Native and
|
|
14
|
+
// the toolkit would both believe they own the offset.
|
|
15
|
+
//
|
|
16
|
+
// So it is drawn, and the geometry is decided here so that all three draw the
|
|
17
|
+
// same thing. That also makes it testable, which is worth more than usual: a
|
|
18
|
+
// scrollbar is pure paint, and paint is what these hosts cannot assert.
|
|
19
|
+
//
|
|
20
|
+
// ## The shape
|
|
21
|
+
//
|
|
22
|
+
// An overlay indicator, like every modern desktop: a rounded bar inset from the
|
|
23
|
+
// trailing edge, sized to the fraction of the content on screen, positioned by
|
|
24
|
+
// how far through it you are. Not a classic scrollbar with arrows and a
|
|
25
|
+
// trough -- nothing here is a drag target yet, which `docs/backlog` records.
|
|
26
|
+
|
|
27
|
+
#pragma once
|
|
28
|
+
|
|
29
|
+
#include "ScrollBounds.h"
|
|
30
|
+
|
|
31
|
+
namespace basalt {
|
|
32
|
+
|
|
33
|
+
// Drawing constants, here rather than in three hosts so they cannot drift.
|
|
34
|
+
// Thin, because it sits over content rather than beside it.
|
|
35
|
+
inline constexpr double kScrollIndicatorThickness = 6.0;
|
|
36
|
+
// From the trailing edge, and from each end of the track.
|
|
37
|
+
inline constexpr double kScrollIndicatorInset = 2.0;
|
|
38
|
+
// Below this a thumb stops reading as a position and starts reading as a dot.
|
|
39
|
+
inline constexpr double kScrollIndicatorMinimumLength = 24.0;
|
|
40
|
+
|
|
41
|
+
struct ScrollIndicator {
|
|
42
|
+
// False when everything fits: a scrollbar for content that cannot scroll is
|
|
43
|
+
// noise, and every list shorter than its container would grow one.
|
|
44
|
+
bool visible{false};
|
|
45
|
+
// Along the track, from its start, in points.
|
|
46
|
+
double offset{0.0};
|
|
47
|
+
double length{0.0};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Where the thumb goes for one axis.
|
|
51
|
+
//
|
|
52
|
+
// `scrollOffset` may be outside the range -- an elastic overscroll goes
|
|
53
|
+
// negative at the top -- and the result is clamped rather than refused, because
|
|
54
|
+
// a thumb that vanished at the limits would flicker at exactly the moment
|
|
55
|
+
// somebody is looking at it.
|
|
56
|
+
//
|
|
57
|
+
// Two sets of insets, and they do different jobs. `content` is the app's
|
|
58
|
+
// `contentInset`: it changes how far the view scrolls, so it changes what
|
|
59
|
+
// fraction of the way through any given offset is. `indicator` is
|
|
60
|
+
// `scrollIndicatorInsets`: it shortens the track the thumb runs in and nothing
|
|
61
|
+
// else, which is what a header overlaying the top of a list wants -- the bar
|
|
62
|
+
// should start below it.
|
|
63
|
+
ScrollIndicator scrollIndicatorFor(double containerLength,
|
|
64
|
+
double contentLength,
|
|
65
|
+
double scrollOffset,
|
|
66
|
+
ScrollAxisInsets content = {},
|
|
67
|
+
ScrollAxisInsets indicator = {});
|
|
68
|
+
|
|
69
|
+
} // namespace basalt
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#include "ScrollMomentum.h"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <cmath>
|
|
5
|
+
|
|
6
|
+
namespace basalt {
|
|
7
|
+
|
|
8
|
+
namespace {
|
|
9
|
+
|
|
10
|
+
// Pixels per second below which a fling is over. Roughly a pixel every two
|
|
11
|
+
// frames, which is slower than a screen can show moving.
|
|
12
|
+
constexpr double kStopSpeed = 30.0;
|
|
13
|
+
|
|
14
|
+
// A fling is not allowed to take longer than this per step. A frame clock that
|
|
15
|
+
// stalls -- a window dragged between monitors, a machine that swapped -- would
|
|
16
|
+
// otherwise hand `advance` a whole second and teleport the list.
|
|
17
|
+
constexpr double kMaxStepSeconds = 0.1;
|
|
18
|
+
|
|
19
|
+
double speed(double x, double y) {
|
|
20
|
+
return std::sqrt(x * x + y * y);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
} // namespace
|
|
24
|
+
|
|
25
|
+
bool ScrollMomentum::start(double velocityX, double velocityY, double decelerationRate) {
|
|
26
|
+
if (speed(velocityX, velocityY) < kStopSpeed) {
|
|
27
|
+
running_ = false;
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
velocityX_ = velocityX;
|
|
31
|
+
velocityY_ = velocityY;
|
|
32
|
+
// Clamped rather than trusted: the prop is a float an app can set to
|
|
33
|
+
// anything, and a rate of 1 or more is a fling that never stops.
|
|
34
|
+
decelerationRate_ = std::clamp(decelerationRate, 0.5, 0.9999);
|
|
35
|
+
running_ = true;
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
bool ScrollMomentum::advance(double seconds, double &dx, double &dy) {
|
|
40
|
+
dx = 0;
|
|
41
|
+
dy = 0;
|
|
42
|
+
if (!running_) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
const double step = std::clamp(seconds, 0.0, kMaxStepSeconds);
|
|
46
|
+
|
|
47
|
+
// The distance covered during the step, at the velocity it started with. A
|
|
48
|
+
// closed-form integral of the decay would be more exact; at sixty steps a
|
|
49
|
+
// second the difference is under a pixel over a whole fling, and this stays
|
|
50
|
+
// readable.
|
|
51
|
+
dx = velocityX_ * step;
|
|
52
|
+
dy = velocityY_ * step;
|
|
53
|
+
|
|
54
|
+
const double decay = std::pow(decelerationRate_, step * 1000.0);
|
|
55
|
+
velocityX_ *= decay;
|
|
56
|
+
velocityY_ *= decay;
|
|
57
|
+
|
|
58
|
+
if (speed(velocityX_, velocityY_) < kStopSpeed) {
|
|
59
|
+
running_ = false;
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
void ScrollMomentum::stop() {
|
|
66
|
+
running_ = false;
|
|
67
|
+
velocityX_ = 0;
|
|
68
|
+
velocityY_ = 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
} // namespace basalt
|