basalt-appkit 0.0.0-rc5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/cli/runMacos.js +33 -0
  3. package/cli/shared.js +75 -0
  4. package/native/CMakeLists.txt +298 -0
  5. package/native/appkit/AppKitAnimationChoreographer.h +54 -0
  6. package/native/appkit/AppKitAnimationChoreographer.mm +125 -0
  7. package/native/appkit/AppKitDropTarget.h +34 -0
  8. package/native/appkit/AppKitDropTarget.mm +292 -0
  9. package/native/appkit/AppKitFocus.h +120 -0
  10. package/native/appkit/AppKitFocus.mm +293 -0
  11. package/native/appkit/AppKitImageLoader.h +80 -0
  12. package/native/appkit/AppKitImageLoader.mm +156 -0
  13. package/native/appkit/AppKitKeyEvents.h +26 -0
  14. package/native/appkit/AppKitKeyEvents.mm +97 -0
  15. package/native/appkit/AppKitMenuBar.mm +424 -0
  16. package/native/appkit/AppKitMountingManager.h +135 -0
  17. package/native/appkit/AppKitMountingManager.mm +946 -0
  18. package/native/appkit/AppKitRunLoopObserver.h +34 -0
  19. package/native/appkit/AppKitRunLoopObserver.mm +42 -0
  20. package/native/appkit/AppKitScrollView.h +168 -0
  21. package/native/appkit/AppKitScrollView.mm +550 -0
  22. package/native/appkit/AppKitSkiaContext.h +33 -0
  23. package/native/appkit/AppKitSkiaContext.mm +255 -0
  24. package/native/appkit/AppKitSkiaModule.h +53 -0
  25. package/native/appkit/AppKitSkiaModule.mm +81 -0
  26. package/native/appkit/AppKitSkiaPeer.h +37 -0
  27. package/native/appkit/AppKitSkiaPeer.mm +123 -0
  28. package/native/appkit/AppKitSkiaView.h +62 -0
  29. package/native/appkit/AppKitSnapshot.h +21 -0
  30. package/native/appkit/AppKitSnapshot.mm +91 -0
  31. package/native/appkit/AppKitTextInput.h +140 -0
  32. package/native/appkit/AppKitTextInput.mm +588 -0
  33. package/native/appkit/AppKitTextPeer.h +74 -0
  34. package/native/appkit/AppKitTextPeer.mm +353 -0
  35. package/native/appkit/AppKitTitleBar.h +90 -0
  36. package/native/appkit/AppKitTitleBar.mm +186 -0
  37. package/native/appkit/AppKitTouchDispatcher.h +138 -0
  38. package/native/appkit/AppKitTouchDispatcher.mm +514 -0
  39. package/native/appkit/AppKitWindowControl.mm +222 -0
  40. package/native/appkit/AppKitWindowModule.h +62 -0
  41. package/native/appkit/AppKitWindowModule.mm +194 -0
  42. package/native/appkit/ColorSchemeAppKit.mm +71 -0
  43. package/native/appkit/ComponentRegistryAppKit.mm +121 -0
  44. package/native/appkit/CoreTextLayout.h +43 -0
  45. package/native/appkit/CoreTextLayout.mm +252 -0
  46. package/native/appkit/CoreTextLayoutManager.mm +138 -0
  47. package/native/appkit/FontRegistryCoreText.mm +148 -0
  48. package/native/appkit/PlatformServicesAppKit.mm +540 -0
  49. package/native/appkit/RnAppKitView.h +417 -0
  50. package/native/appkit/RnAppKitView.mm +1605 -0
  51. package/native/appkit/RnTextLayout.h +38 -0
  52. package/native/appkit/RnTextLayout.mm +168 -0
  53. package/native/appkit/demo_layout_appkit.mm +93 -0
  54. package/native/appkit/main_appkit.mm +1860 -0
  55. package/native/appkit/mount_harness_appkit.mm +197 -0
  56. package/native/tests/test_appkit_accessibility.mm +170 -0
  57. package/native/tests/test_appkit_image.mm +237 -0
  58. package/native/tests/test_appkit_input.mm +852 -0
  59. package/native/tests/test_appkit_key_events.mm +150 -0
  60. package/native/tests/test_appkit_mounting.mm +430 -0
  61. package/native/tests/test_appkit_scrollbar.mm +132 -0
  62. package/native/tests/test_appkit_services.mm +57 -0
  63. package/native/tests/test_appkit_skia.mm +212 -0
  64. package/native/tests/test_appkit_text.mm +180 -0
  65. package/native/tests/test_appkit_textinput.mm +748 -0
  66. package/native/tests/test_appkit_titlebar.mm +107 -0
  67. package/native/tests/test_appkit_view.mm +237 -0
  68. package/package.json +26 -0
  69. package/react-native.config.js +22 -0
@@ -0,0 +1,34 @@
1
+ // Driving React Native's event beat from the main run loop.
2
+ //
3
+ // Nothing an EventEmitter produces reaches JavaScript on its own.
4
+ // `EventQueue::onEnqueue` only sets a flag on the EventBeat; the queue is
5
+ // flushed when something calls `RunLoopObserverManager::onRender()`, which
6
+ // induces the beat. Until that is wired, touches are enqueued and silently
7
+ // never delivered.
8
+ //
9
+ // The observer React Native asks for is `Activity::BeforeWaiting`: run just
10
+ // before the loop goes to sleep, having drained whatever else was pending. On
11
+ // iOS that is a CFRunLoopObserver, and this is the same thing -- the GTK side
12
+ // has to build the equivalent out of a GSource, which is the more interesting
13
+ // half of the comment in gtk/GtkRunLoopObserver.h.
14
+
15
+ #pragma once
16
+
17
+ #import <CoreFoundation/CoreFoundation.h>
18
+
19
+ #include <memory>
20
+
21
+ namespace facebook::react {
22
+ class RunLoopObserverManager;
23
+ }
24
+
25
+ namespace basalt {
26
+
27
+ // Installs the observer on the main run loop. Returns it, and the caller owns
28
+ // it: remove it with removeRunLoopObserver before the manager goes away.
29
+ CFRunLoopObserverRef installRunLoopObserver(
30
+ std::shared_ptr<facebook::react::RunLoopObserverManager> manager);
31
+
32
+ void removeRunLoopObserver(CFRunLoopObserverRef observer);
33
+
34
+ } // namespace basalt
@@ -0,0 +1,42 @@
1
+ #import "AppKitRunLoopObserver.h"
2
+
3
+ #include <react/utils/RunLoopObserverManager.h>
4
+
5
+ namespace basalt {
6
+
7
+ CFRunLoopObserverRef installRunLoopObserver(
8
+ std::shared_ptr<facebook::react::RunLoopObserverManager> manager) {
9
+ // The block captures the shared_ptr, so the manager outlives any beat already
10
+ // in flight. CFRunLoopObserverCreateWithHandler copies the block onto the
11
+ // heap and releases it when the observer is released.
12
+ CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(
13
+ kCFAllocatorDefault,
14
+ kCFRunLoopBeforeWaiting,
15
+ /*repeats=*/true,
16
+ // Order: after AppKit's own observers, so this runs having let the frame
17
+ // settle rather than ahead of it. The GTK side makes the same choice with
18
+ // G_PRIORITY_DEFAULT_IDLE, for the same reason.
19
+ /*order=*/CFIndex{2000000},
20
+ ^(CFRunLoopObserverRef /*self*/, CFRunLoopActivity /*activity*/) {
21
+ if (manager != nullptr) {
22
+ manager->onRender();
23
+ }
24
+ });
25
+
26
+ // Both modes. The default mode is where AppKit spends its time idle; a modal
27
+ // panel or a live window resize switches to another, and a beat that stopped
28
+ // there would stop delivering every event JavaScript is waiting on until the
29
+ // user let go of the mouse.
30
+ CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
31
+ return observer;
32
+ }
33
+
34
+ void removeRunLoopObserver(CFRunLoopObserverRef observer) {
35
+ if (observer == nullptr) {
36
+ return;
37
+ }
38
+ CFRunLoopRemoveObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
39
+ CFRelease(observer);
40
+ }
41
+
42
+ } // namespace basalt
@@ -0,0 +1,168 @@
1
+ // <ScrollView> on AppKit.
2
+ //
3
+ // Yoga does the hard part. A ScrollView's Yoga node carries `overflow: scroll`,
4
+ // which lets its child exceed the viewport instead of being clamped to it, so
5
+ // by the time a mutation arrives the content is already laid out at its full
6
+ // size and the only thing missing is the offset.
7
+ //
8
+ // Structure: a mounted ScrollView has exactly **one** child, a content view.
9
+ // React Native's JS wraps the children in an `RCTScrollContentView`, which the
10
+ // C++ registry rewrites to a plain `View` (`componentNameByReactViewName`), so
11
+ // no extra descriptor is needed and nothing here should expect N children.
12
+ //
13
+ // Scrolling is `bounds.origin` on the ScrollView's own view, not an
14
+ // `NSScrollView`. An NSScrollView wants to own its document view's size and
15
+ // bring a clip view, scrollers and elasticity with it; this platform's whole
16
+ // invariant is that React Native decides sizes and the platform only places.
17
+ // Moving the bounds keeps that invariant and makes AppKit's drawing, clipping
18
+ // and hit testing follow the scroll for free. The GTK side reaches the same
19
+ // place by shifting children in its layout manager.
20
+ //
21
+ // Two things must happen on every scroll, and they are not the same thing:
22
+ //
23
+ // - `onScroll` goes to JavaScript, throttled by `scrollEventThrottle`. Without
24
+ // it VirtualizedList never renders past its first window.
25
+ // - `contentOffset` is written back into `ScrollViewState`, unthrottled.
26
+ // `ScrollViewShadowNode::getContentOriginOffset` reads it, and through that
27
+ // so do `measure`, `measureLayout`, C++ hit testing and view culling. Skip
28
+ // it and those all silently report unscrolled coordinates.
29
+ //
30
+ // All of that is the same on both desktops, which is why this file reads like
31
+ // GtkScrollView.cpp with different event plumbing.
32
+
33
+ #pragma once
34
+
35
+ #import "RnAppKitView.h"
36
+
37
+ #include "ScrollAnimation.h"
38
+ #include "ScrollBounds.h"
39
+ #include "ScrollIndicator.h"
40
+ #include "ScrollSnap.h"
41
+
42
+ #include <react/renderer/components/scrollview/ScrollViewShadowNode.h>
43
+ #include <react/renderer/core/EventEmitter.h>
44
+ #include <react/renderer/mounting/ShadowView.h>
45
+
46
+ #include <functional>
47
+ #include <unordered_map>
48
+
49
+ namespace basalt {
50
+
51
+ class AppKitScrollViewManager {
52
+ public:
53
+ // Emitters live in the mounting manager's registry, which is also what owns
54
+ // this, so they are reached through a lookup rather than a second copy.
55
+ using EmitterLookup = std::function<facebook::react::EventEmitter::Shared(facebook::react::Tag)>;
56
+
57
+ explicit AppKitScrollViewManager(EmitterLookup lookup);
58
+ ~AppKitScrollViewManager();
59
+
60
+ AppKitScrollViewManager(const AppKitScrollViewManager &) = delete;
61
+ AppKitScrollViewManager &operator=(const AppKitScrollViewManager &) = delete;
62
+
63
+ // Called for every mutation touching a ScrollView. Registers the view the
64
+ // first time, and refreshes the geometry and props after.
65
+ void update(RnAppKitView *view, const facebook::react::ShadowView &shadowView);
66
+
67
+ void remove(facebook::react::Tag tag);
68
+
69
+ // ScrollView's imperative commands: scrollTo, scrollToEnd. Returns false if
70
+ // the command is not one this handles.
71
+ bool dispatchCommand(facebook::react::Tag tag,
72
+ const std::string &name,
73
+ const folly::dynamic &args);
74
+
75
+ // The wheel, from the Objective-C trampoline. Returns false when the scroll
76
+ // was not consumed, which lets AppKit walk it up the responder chain.
77
+ //
78
+ // `began`/`ended` are the fingers on a touchpad, and become onScrollBeginDrag
79
+ // and onScrollEndDrag. `momentumBegan`/`momentumEnded` are the coasting that
80
+ // follows them, and become onMomentumScrollBegin and onMomentumScrollEnd. A
81
+ // wheel has neither: it reports no phase at all, so it is a run of plain
82
+ // scrolls, which is what it is.
83
+ bool scrollBy(facebook::react::Tag tag,
84
+ double dx,
85
+ double dy,
86
+ bool began,
87
+ bool ended,
88
+ bool momentumBegan,
89
+ bool momentumEnded);
90
+
91
+ // The pull past the top of the list, which is what a <RefreshControl> is
92
+ // waiting for. Called with how far past the top this scroll asked to go, or
93
+ // with zero when the gesture went the other way or the view actually moved --
94
+ // which re-arms it. See core/PullToRefresh.h for why a desktop has to count
95
+ // this rather than measure a rubber band.
96
+ void setOverscrollTopHandler(std::function<void(facebook::react::Tag, double)> handler) {
97
+ overscrollTop_ = std::move(handler);
98
+ }
99
+
100
+ private:
101
+ std::function<void(facebook::react::Tag, double)> overscrollTop_;
102
+
103
+ struct Entry {
104
+ RnAppKitView *view{nil};
105
+ facebook::react::Tag tag{0};
106
+
107
+ std::shared_ptr<const facebook::react::ScrollViewShadowNode::ConcreteState> state;
108
+
109
+ facebook::react::Size contentSize{};
110
+ facebook::react::Size containerSize{};
111
+ // `contentInset` changes how far the view scrolls;
112
+ // `scrollIndicatorInsets` changes only where the bar is drawn. See
113
+ // core/ScrollBounds.h.
114
+ facebook::react::EdgeInsets contentInset{};
115
+ facebook::react::EdgeInsets indicatorInset{};
116
+
117
+ bool scrollEnabled{true};
118
+ // `showsVerticalScrollIndicator` / `showsHorizontalScrollIndicator`. Both
119
+ // default to true, as React Native's own props do.
120
+ bool showsVerticalIndicator{true};
121
+ bool showsHorizontalIndicator{true};
122
+ // Milliseconds, as React Native's prop is. Zero means every scroll.
123
+ double eventThrottleMs{0};
124
+
125
+ double offsetX{0};
126
+ double offsetY{0};
127
+ double lastEmitSeconds{0};
128
+ bool dragging{false};
129
+ // Coasting after the fingers left. Tracked for the same reason `dragging`
130
+ // is: a momentum end that was never begun would leave JavaScript believing
131
+ // a scroll it never heard start has finished.
132
+ bool coasting{false};
133
+
134
+ // An animated `scrollTo`. AppKit does its own deceleration, so unlike GTK
135
+ // there is no fling stepper here to borrow -- this brings its own display
136
+ // link, created when a curve starts and invalidated when it ends.
137
+ ScrollSnapConfig snap;
138
+ ScrollAnimation animation;
139
+ id displayLink{nil};
140
+ double animationLastSeconds{0};
141
+ };
142
+
143
+ void applyOffset(Entry &entry, double x, double y, bool emitEvent);
144
+ // Recomputes both overlay scrollbars and hands them to the view to draw.
145
+ // Called wherever the offset, the content size or the container size can
146
+ // have changed, which is more places than the offset alone.
147
+ void updateIndicators(const Entry &entry);
148
+ void scrollTowards(Entry &entry, double x, double y, bool animated);
149
+ bool settleOnSnapPoint(Entry &entry, double velocityY);
150
+ void stopAnimation(Entry &entry);
151
+
152
+ public:
153
+ // Called from the display link's target, which is an Objective-C object and
154
+ // cannot reach a private member.
155
+ void advanceAnimation(facebook::react::Tag tag, double seconds);
156
+
157
+ private:
158
+ void emitScrollEvent(Entry &entry, const char *which);
159
+ void writeStateOffset(const Entry &entry);
160
+
161
+ EmitterLookup lookup_;
162
+ std::unordered_map<facebook::react::Tag, Entry> entries_;
163
+ // The object every ScrollView's view points at. One for all of them: it
164
+ // dispatches by tag, so there is nothing per-view to keep in step.
165
+ id scrollTarget_;
166
+ };
167
+
168
+ } // namespace basalt