basalt-gtk 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 (71) hide show
  1. package/LICENSE +21 -0
  2. package/cli/runLinux.js +29 -0
  3. package/cli/shared.js +75 -0
  4. package/native/CMakeLists.txt +362 -0
  5. package/native/gtk/ColorSchemeGtk.cpp +58 -0
  6. package/native/gtk/ComponentRegistryGtk.cpp +101 -0
  7. package/native/gtk/FontRegistryFontconfig.cpp +127 -0
  8. package/native/gtk/GtkAnimationChoreographer.cpp +73 -0
  9. package/native/gtk/GtkAnimationChoreographer.h +49 -0
  10. package/native/gtk/GtkDropTarget.cpp +269 -0
  11. package/native/gtk/GtkDropTarget.h +40 -0
  12. package/native/gtk/GtkFocus.cpp +296 -0
  13. package/native/gtk/GtkFocus.h +113 -0
  14. package/native/gtk/GtkImageLoader.cpp +128 -0
  15. package/native/gtk/GtkImageLoader.h +87 -0
  16. package/native/gtk/GtkKeyEvents.cpp +91 -0
  17. package/native/gtk/GtkKeyEvents.h +21 -0
  18. package/native/gtk/GtkMenuBar.cpp +45 -0
  19. package/native/gtk/GtkMountingManager.cpp +1027 -0
  20. package/native/gtk/GtkMountingManager.h +146 -0
  21. package/native/gtk/GtkRunLoopObserver.cpp +77 -0
  22. package/native/gtk/GtkRunLoopObserver.h +41 -0
  23. package/native/gtk/GtkScrollView.cpp +626 -0
  24. package/native/gtk/GtkScrollView.h +209 -0
  25. package/native/gtk/GtkSkiaContext.cpp +248 -0
  26. package/native/gtk/GtkSkiaContext.h +62 -0
  27. package/native/gtk/GtkSkiaModule.cpp +80 -0
  28. package/native/gtk/GtkSkiaModule.h +51 -0
  29. package/native/gtk/GtkSkiaPeer.cpp +285 -0
  30. package/native/gtk/GtkSkiaPeer.h +63 -0
  31. package/native/gtk/GtkTextInput.cpp +428 -0
  32. package/native/gtk/GtkTextInput.h +123 -0
  33. package/native/gtk/GtkTextPeer.cpp +425 -0
  34. package/native/gtk/GtkTextPeer.h +87 -0
  35. package/native/gtk/GtkTitleBar.cpp +273 -0
  36. package/native/gtk/GtkTitleBar.h +96 -0
  37. package/native/gtk/GtkTitleBarLayout.cpp +45 -0
  38. package/native/gtk/GtkTitleBarLayout.h +36 -0
  39. package/native/gtk/GtkTouchDispatcher.cpp +671 -0
  40. package/native/gtk/GtkTouchDispatcher.h +139 -0
  41. package/native/gtk/GtkWindowControl.cpp +223 -0
  42. package/native/gtk/GtkWindowModule.cpp +194 -0
  43. package/native/gtk/GtkWindowModule.h +62 -0
  44. package/native/gtk/PangoTextLayout.cpp +295 -0
  45. package/native/gtk/PangoTextLayout.h +54 -0
  46. package/native/gtk/PangoTextLayoutManager.cpp +140 -0
  47. package/native/gtk/PlatformServicesGtk.cpp +583 -0
  48. package/native/gtk/RnView.cpp +1407 -0
  49. package/native/gtk/RnView.h +339 -0
  50. package/native/gtk/demo_layout_gtk.cpp +57 -0
  51. package/native/gtk/main_gtk.cpp +1981 -0
  52. package/native/gtk/mount_harness_gtk.cpp +143 -0
  53. package/native/tests/TestMainGtk.cpp +25 -0
  54. package/native/tests/notification_stub.cpp +103 -0
  55. package/native/tests/test_accessibility.cpp +251 -0
  56. package/native/tests/test_focus.cpp +194 -0
  57. package/native/tests/test_hittest.cpp +242 -0
  58. package/native/tests/test_image.cpp +135 -0
  59. package/native/tests/test_key_events_gtk.cpp +112 -0
  60. package/native/tests/test_mounting.cpp +415 -0
  61. package/native/tests/test_scrollview.cpp +258 -0
  62. package/native/tests/test_skia_texture.cpp +120 -0
  63. package/native/tests/test_text.cpp +147 -0
  64. package/native/tests/test_textinput.cpp +598 -0
  65. package/native/tests/test_titlebar.cpp +137 -0
  66. package/native/tests/test_titlebar_drag.cpp +138 -0
  67. package/native/tests/test_version.cpp +49 -0
  68. package/native/tests/test_view.cpp +162 -0
  69. package/native/tests/test_viewprops.cpp +254 -0
  70. package/package.json +26 -0
  71. package/react-native.config.js +21 -0
@@ -0,0 +1,146 @@
1
+ // basalt-core — Fabric mounting layer
2
+ //
3
+ // Implements ReactCxxPlatform's IMountingManager: the single interface
4
+ // ReactHost requires in order to put a React Native surface on screen.
5
+ // Everything below translates ShadowViewMutation into GTK4 widget operations.
6
+
7
+ #pragma once
8
+
9
+ #include "GtkImageLoader.h"
10
+ #include "GtkScrollView.h"
11
+ #include "GtkTextInput.h"
12
+ #include "MountingWalk.h"
13
+ #include "RnView.h"
14
+
15
+ #include <react/renderer/uimanager/IMountingManager.h>
16
+
17
+ #include <memory>
18
+ #include <unordered_map>
19
+
20
+ namespace basalt {
21
+
22
+ // The mutation walk, the registry and the surface-root handling all live in
23
+ // MountingWalk and are shared with every other desktop platform. What is left
24
+ // here is the part that is genuinely GTK: making a widget, parenting it, and
25
+ // turning a ShadowView into widget state.
26
+ class GtkMountingManager final : public facebook::react::IMountingManager,
27
+ public MountingWalk<GtkMountingManager, RnView *> {
28
+ public:
29
+
30
+ // The image loader, for the host to hand to React Native's
31
+ // `ImageLoaderModule` -- which is what `Image.getSize` and `Image.prefetch`
32
+ // reach. Shared so that module can hold a weak reference to it.
33
+ std::shared_ptr<GtkImageLoader> imageLoader() const { return imageLoader_; }
34
+ GtkMountingManager();
35
+ ~GtkMountingManager() noexcept override;
36
+
37
+ // --- IMountingManager -----------------------------------------------------
38
+
39
+ // Called on the JS thread, from the event loop's "update the rendering"
40
+ // step. Hands the transaction to the GTK main thread; does not touch widgets.
41
+ void executeMount(facebook::react::SurfaceId surfaceId,
42
+ facebook::react::MountingTransaction &&transaction) override;
43
+
44
+ void dispatchCommand(const facebook::react::ShadowView &shadowView,
45
+ const std::string &commandName,
46
+ const folly::dynamic &args) override;
47
+
48
+ facebook::react::ComponentRegistryFactory getComponentRegistryFactory() override;
49
+
50
+ bool hasComponent(const std::string &name) override;
51
+
52
+ // ReactCxxPlatform hands the UIManager to the mounting manager and to nothing
53
+ // else. Reanimated needs it, and is constructed from JavaScript where no host
54
+ // object is in reach, so this passes it on. See core/UIManagerAccess.h.
55
+ void setUIManager(std::weak_ptr<facebook::react::UIManager> uiManager) noexcept override;
56
+
57
+ // --- Host-facing ----------------------------------------------------------
58
+ //
59
+ // createSurfaceRoot, destroySurfaceRoot, getSurfaceRoot, viewForTag and
60
+ // eventEmitterForTag come from MountingWalk. Callers cast the emitter to what
61
+ // they need: GtkTouchDispatcher to TouchEventEmitter, the image path to
62
+ // ImageEventEmitter.
63
+
64
+ // Applies a transaction to the widget tree. Invoked on the GTK main thread by
65
+ // executeMount's marshalling -- not part of IMountingManager, and never to be
66
+ // called directly.
67
+ // The main-thread half of dispatchCommand, public for the same reason
68
+ // applyTransaction is: the idle callback that carries it across has to call
69
+ // it, and the tests call it directly rather than spinning a main loop.
70
+ void applyCommand(facebook::react::Tag tag,
71
+ const std::string &commandName,
72
+ const folly::dynamic &args);
73
+
74
+ // A press ended on this view. A <Switch> is toggled from React Native's own
75
+ // touch path rather than by the GtkSwitch's own input, so that a press means
76
+ // the same thing on three desktops -- and so that the same synthesised tap
77
+ // the rest of the suite uses can reach it. See applyControlPeer.
78
+ //
79
+ // A no-op for every view that is not a switch.
80
+ void pressedView(facebook::react::Tag tag);
81
+
82
+ void applyTransaction(facebook::react::SurfaceId surfaceId,
83
+ facebook::react::MountingTransaction &&transaction);
84
+
85
+ // The scroll views, for the host's BASALT_TEST_SCROLL instrument. Nothing
86
+ // else reaches in here: a real wheel arrives at the GtkEventControllerScroll
87
+ // the manager attached itself.
88
+ GtkScrollViewManager &scrollViews() {
89
+ return scrollViews_;
90
+ }
91
+
92
+ private:
93
+ // --- What MountingWalk asks of a platform ---------------------------------
94
+ friend class MountingWalk<GtkMountingManager, RnView *>;
95
+
96
+ RnView *createView(const facebook::react::ShadowView &shadowView);
97
+ RnView *createRootView(facebook::react::Tag tag);
98
+ void destroyView(RnView *view);
99
+ void insertChild(RnView *parent, RnView *child, int index);
100
+ void removeChild(RnView *parent, RnView *child);
101
+ void updateView(RnView *view, const facebook::react::ShadowView &shadowView);
102
+ void forgetTag(facebook::react::Tag tag);
103
+
104
+ // The GTK half of a control: a GtkSpinner for <ActivityIndicator> and
105
+ // <RefreshControl>, a GtkSwitch for <Switch>. Everything that is not a
106
+ // widget -- which props mean what, which scroll view a refresh control
107
+ // belongs to, when `onShow` fires -- is in MountingWalk.
108
+ void applyControlPeer(RnView *view, const basalt::ControlState &state);
109
+
110
+ // React DevTools' overlay rectangles. The parsing and the clearing are in
111
+ // MountingWalk; this is the drawing.
112
+ void setHighlights(RnView *view, const std::vector<basalt::Highlight> &highlights);
113
+
114
+ // --- The GTK half of updateView -------------------------------------------
115
+ void applyProps(RnView *view, const facebook::react::ShadowView &shadowView);
116
+ void applyText(RnView *view, const facebook::react::ShadowView &shadowView);
117
+ void applyImage(RnView *view, const facebook::react::ShadowView &shadowView);
118
+ void applyScrollView(RnView *view, const facebook::react::ShadowView &shadowView);
119
+ void applyAccessibility(RnView *view, const facebook::react::ShadowView &shadowView);
120
+ void applyTextInput(RnView *view, const facebook::react::ShadowView &shadowView);
121
+ void applyLayoutMetrics(RnView *view, const facebook::react::ShadowView &shadowView);
122
+
123
+ // Shared rather than held by value: React Native's `ImageLoaderModule` takes
124
+ // a `weak_ptr<IImageLoader>`, and this is the loader it gets. See
125
+ // `imageLoader()`.
126
+ std::shared_ptr<GtkImageLoader> imageLoader_ = std::make_shared<GtkImageLoader>();
127
+ GtkScrollViewManager scrollViews_;
128
+ GtkTextInputManager textInputs_;
129
+
130
+ // A <Switch> is a controlled component: the widget is not allowed to decide
131
+ // its own state, so the value React last sent is kept here and put straight
132
+ // back after the toggle that told JavaScript about it. React Native's iOS
133
+ // switch does exactly this.
134
+ std::unordered_map<facebook::react::Tag, bool> switchValues_;
135
+
136
+ // The CSS class each tinted control is currently carrying, so the next tint
137
+ // can take it off again. Interned strings owned by the table in the .cpp,
138
+ // never freed here.
139
+ std::unordered_map<facebook::react::Tag, const char *> controlClasses_;
140
+
141
+ // The source each <Image> is currently showing, so that a mutation which
142
+ // changed only layout does not restart the load.
143
+ std::unordered_map<facebook::react::Tag, std::string> imageUris_;
144
+ };
145
+
146
+ } // namespace basalt
@@ -0,0 +1,77 @@
1
+ #include "GtkRunLoopObserver.h"
2
+
3
+ #include <react/utils/RunLoopObserverManager.h>
4
+
5
+ namespace basalt {
6
+
7
+ namespace {
8
+
9
+ struct BeatSource {
10
+ GSource source;
11
+ // A bare pointer into a shared_ptr held below. GSource is a C struct with no
12
+ // destructor of its own, so the owning reference lives in a member that
13
+ // finalize() destroys explicitly.
14
+ std::shared_ptr<facebook::react::RunLoopObserverManager> *manager;
15
+ };
16
+
17
+ // Called once per main-loop iteration, before the loop polls. Returning FALSE
18
+ // with no timeout means "I am never ready", so this source never causes a
19
+ // wake-up and never spins: it only rides along on iterations that something
20
+ // else caused.
21
+ gboolean beatPrepare(GSource *source, gint *timeout) {
22
+ auto *self = reinterpret_cast<BeatSource *>(source);
23
+ if (self->manager != nullptr && *self->manager != nullptr) {
24
+ (*self->manager)->onRender();
25
+ }
26
+ *timeout = -1;
27
+ return FALSE;
28
+ }
29
+
30
+ gboolean beatCheck(GSource * /*source*/) {
31
+ return FALSE;
32
+ }
33
+
34
+ gboolean beatDispatch(GSource * /*source*/, GSourceFunc /*callback*/, gpointer /*userData*/) {
35
+ return G_SOURCE_CONTINUE;
36
+ }
37
+
38
+ void beatFinalize(GSource *source) {
39
+ auto *self = reinterpret_cast<BeatSource *>(source);
40
+ delete self->manager;
41
+ self->manager = nullptr;
42
+ }
43
+
44
+ GSourceFuncs beatSourceFuncs = {
45
+ .prepare = beatPrepare,
46
+ .check = beatCheck,
47
+ .dispatch = beatDispatch,
48
+ .finalize = beatFinalize,
49
+ .closure_callback = nullptr,
50
+ .closure_marshal = nullptr,
51
+ };
52
+
53
+ } // namespace
54
+
55
+ GSource *installRunLoopObserver(std::shared_ptr<facebook::react::RunLoopObserverManager> manager) {
56
+ GSource *source = g_source_new(&beatSourceFuncs, sizeof(BeatSource));
57
+ auto *self = reinterpret_cast<BeatSource *>(source);
58
+ self->manager = new std::shared_ptr<facebook::react::RunLoopObserverManager>(std::move(manager));
59
+
60
+ // Below the default, so this runs after ordinary work has been queued rather
61
+ // than ahead of it. prepare() is called regardless of priority, but keeping
62
+ // it low makes the intent clear.
63
+ g_source_set_priority(source, G_PRIORITY_DEFAULT_IDLE);
64
+ g_source_set_name(source, "react-native event beat");
65
+ g_source_attach(source, nullptr);
66
+ return source;
67
+ }
68
+
69
+ void removeRunLoopObserver(GSource *source) {
70
+ if (source == nullptr) {
71
+ return;
72
+ }
73
+ g_source_destroy(source);
74
+ g_source_unref(source);
75
+ }
76
+
77
+ } // namespace basalt
@@ -0,0 +1,41 @@
1
+ // Driving React Native's event beat from the GTK main 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 -- which is exactly how this platform behaved before input
8
+ // existed, and why it was not noticed earlier.
9
+ //
10
+ // The observer React Native asks for is `Activity::BeforeWaiting`: run just
11
+ // before the loop goes to sleep, having drained whatever else was pending. On
12
+ // iOS that is a CFRunLoopObserver, and on Android the Choreographer.
13
+ //
14
+ // The GLib equivalent is a GSource that does its work in `prepare()` and never
15
+ // reports itself ready. `prepare()` runs once per main-loop iteration, before
16
+ // the poll, and an idle loop blocks in that poll rather than spinning -- so
17
+ // this costs one function call per iteration when busy and nothing at all when
18
+ // the application is idle. A tick callback on the frame clock would also work,
19
+ // but it would hold the frame clock open and wake the process at display rate
20
+ // forever, which is the wrong trade for a desktop app that is usually still.
21
+
22
+ #pragma once
23
+
24
+ #include <glib.h>
25
+
26
+ #include <memory>
27
+
28
+ namespace facebook::react {
29
+ class RunLoopObserverManager;
30
+ }
31
+
32
+ namespace basalt {
33
+
34
+ // Installs the source on the default main context. Returns the GSource, which
35
+ // the caller owns: destroy it with removeRunLoopObserver before the manager it
36
+ // refers to goes away.
37
+ GSource *installRunLoopObserver(std::shared_ptr<facebook::react::RunLoopObserverManager> manager);
38
+
39
+ void removeRunLoopObserver(GSource *source);
40
+
41
+ } // namespace basalt