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,127 @@
1
+ #include "FontRegistry.h"
2
+
3
+ #include <pango/pangocairo.h>
4
+ #include <pango/pangofc-fontmap.h>
5
+
6
+ #include <fontconfig/fontconfig.h>
7
+ // FcFreeTypeQuery lives here rather than in fontconfig.h, and it is what reads
8
+ // the family name out of the file.
9
+ #include <fontconfig/fcfreetype.h>
10
+
11
+ #include <mutex>
12
+ #include <unordered_map>
13
+ #include <vector>
14
+
15
+ namespace basalt {
16
+
17
+ namespace {
18
+
19
+ struct Registry {
20
+ std::mutex mutex;
21
+ unsigned long generation{0};
22
+ // What the app calls it -> what the file calls itself.
23
+ std::unordered_map<std::string, std::string> families;
24
+ std::vector<std::string> order;
25
+ };
26
+
27
+ Registry &registry() {
28
+ static Registry instance;
29
+ return instance;
30
+ }
31
+
32
+ // The family name recorded inside the file. fontconfig will happily add a file
33
+ // and then index it under a name the app has never heard of, so this is read
34
+ // back rather than assumed.
35
+ std::string familyInFile(const std::string &path) {
36
+ FcBlanks *blanks = FcConfigGetBlanks(FcConfigGetCurrent());
37
+ int count = 0;
38
+ FcPattern *pattern =
39
+ FcFreeTypeQuery(reinterpret_cast<const FcChar8 *>(path.c_str()), 0, blanks, &count);
40
+ if (pattern == nullptr) {
41
+ return {};
42
+ }
43
+
44
+ FcChar8 *family = nullptr;
45
+ std::string result;
46
+ if (FcPatternGetString(pattern, FC_FAMILY, 0, &family) == FcResultMatch && family != nullptr) {
47
+ result = reinterpret_cast<const char *>(family);
48
+ }
49
+ FcPatternDestroy(pattern);
50
+ return result;
51
+ }
52
+
53
+ } // namespace
54
+
55
+ bool registerFont(const std::string &name, const std::string &path) {
56
+ Registry &state = registry();
57
+ const std::lock_guard<std::mutex> lock(state.mutex);
58
+
59
+ if (state.families.find(name) != state.families.end()) {
60
+ return true;
61
+ }
62
+
63
+ if (FcInit() == FcFalse) {
64
+ return false;
65
+ }
66
+ if (FcConfigAppFontAddFile(FcConfigGetCurrent(),
67
+ reinterpret_cast<const FcChar8 *>(path.c_str())) == FcFalse) {
68
+ return false;
69
+ }
70
+
71
+ const std::string family = familyInFile(path);
72
+ if (family.empty()) {
73
+ // Added to fontconfig but unusable: without the real family there is no way
74
+ // to ask Pango for it, and pretending otherwise would fail later and
75
+ // further away.
76
+ return false;
77
+ }
78
+
79
+ state.families.emplace(name, family);
80
+ state.order.push_back(name);
81
+ state.generation++;
82
+
83
+ // Pango caches which fonts exist. Adding one to fontconfig behind its back
84
+ // leaves it looking at the old list, so the family would not be found until
85
+ // something else happened to invalidate it. Only meaningful for a fontconfig
86
+ // backed map, which is not the only kind Pango has.
87
+ PangoFontMap *fontMap = pango_cairo_font_map_get_default();
88
+ if (PANGO_IS_FC_FONT_MAP(fontMap)) {
89
+ pango_fc_font_map_config_changed(PANGO_FC_FONT_MAP(fontMap));
90
+ }
91
+
92
+ return true;
93
+ }
94
+
95
+ unsigned long fontGeneration() {
96
+ Registry &state = registry();
97
+ const std::lock_guard<std::mutex> lock(state.mutex);
98
+ return state.generation;
99
+ }
100
+
101
+ std::string resolveFontFamily(const std::string &name) {
102
+ Registry &state = registry();
103
+ const std::lock_guard<std::mutex> lock(state.mutex);
104
+ const auto it = state.families.find(name);
105
+ return it == state.families.end() ? name : it->second;
106
+ }
107
+
108
+ bool isFontRegistered(const std::string &name) {
109
+ Registry &state = registry();
110
+ const std::lock_guard<std::mutex> lock(state.mutex);
111
+ return state.families.find(name) != state.families.end();
112
+ }
113
+
114
+ std::string registeredFontNamesJoined(char separator) {
115
+ Registry &state = registry();
116
+ const std::lock_guard<std::mutex> lock(state.mutex);
117
+ std::string joined;
118
+ for (const auto &name : state.order) {
119
+ if (!joined.empty()) {
120
+ joined.push_back(separator);
121
+ }
122
+ joined += name;
123
+ }
124
+ return joined;
125
+ }
126
+
127
+ } // namespace basalt
@@ -0,0 +1,73 @@
1
+ #include "GtkAnimationChoreographer.h"
2
+
3
+ namespace basalt {
4
+
5
+ GtkAnimationChoreographer::~GtkAnimationChoreographer() {
6
+ detach();
7
+ }
8
+
9
+ void GtkAnimationChoreographer::onFrameClockUpdate(GdkFrameClock * /*clock*/, gpointer userData) {
10
+ auto *self = static_cast<GtkAnimationChoreographer *>(userData);
11
+ self->onAnimationFrame(self->now());
12
+ }
13
+
14
+ gboolean GtkAnimationChoreographer::syncRunningStateOnMainThread(gpointer userData) {
15
+ auto *self = static_cast<GtkAnimationChoreographer *>(userData);
16
+ self->setUpdating(self->running_.load(std::memory_order_acquire));
17
+ return G_SOURCE_REMOVE;
18
+ }
19
+
20
+ void GtkAnimationChoreographer::setUpdating(bool shouldUpdate) {
21
+ if (frameClock_ == nullptr || shouldUpdate == updating_) {
22
+ return;
23
+ }
24
+ if (shouldUpdate) {
25
+ gdk_frame_clock_begin_updating(frameClock_);
26
+ } else {
27
+ gdk_frame_clock_end_updating(frameClock_);
28
+ }
29
+ updating_ = shouldUpdate;
30
+ }
31
+
32
+ void GtkAnimationChoreographer::attachToWidget(GtkWidget *widget) {
33
+ detach();
34
+
35
+ GdkFrameClock *clock = gtk_widget_get_frame_clock(widget);
36
+ if (clock == nullptr) {
37
+ g_warning("GtkAnimationChoreographer: widget has no frame clock (not "
38
+ "realised yet); animations will not tick");
39
+ return;
40
+ }
41
+
42
+ frameClock_ = GDK_FRAME_CLOCK(g_object_ref(clock));
43
+ updateHandler_ = g_signal_connect(frameClock_, "update",
44
+ G_CALLBACK(onFrameClockUpdate), this);
45
+
46
+ // A resume() may have arrived before the widget was realised.
47
+ setUpdating(running_.load(std::memory_order_acquire));
48
+ }
49
+
50
+ void GtkAnimationChoreographer::detach() {
51
+ if (frameClock_ == nullptr) {
52
+ return;
53
+ }
54
+ setUpdating(false);
55
+ if (updateHandler_ != 0) {
56
+ g_signal_handler_disconnect(frameClock_, updateHandler_);
57
+ updateHandler_ = 0;
58
+ }
59
+ g_object_unref(frameClock_);
60
+ frameClock_ = nullptr;
61
+ }
62
+
63
+ void GtkAnimationChoreographer::resume() {
64
+ running_.store(true, std::memory_order_release);
65
+ g_idle_add_full(G_PRIORITY_DEFAULT, syncRunningStateOnMainThread, this, nullptr);
66
+ }
67
+
68
+ void GtkAnimationChoreographer::pause() {
69
+ running_.store(false, std::memory_order_release);
70
+ g_idle_add_full(G_PRIORITY_DEFAULT, syncRunningStateOnMainThread, this, nullptr);
71
+ }
72
+
73
+ } // namespace basalt
@@ -0,0 +1,49 @@
1
+ // Drives React Native's animation frame callbacks from GTK's frame clock.
2
+ //
3
+ // AnimationChoreographer is the frame-callback seam in ReactCxxPlatform. It is
4
+ // NOT what drives mounting -- mounts arrive on the JS thread via the
5
+ // RuntimeScheduler and are marshalled by GtkMountingManager. This is purely
6
+ // the "there is a new frame" signal the animation backend needs.
7
+
8
+ #pragma once
9
+
10
+ #include <gtk/gtk.h>
11
+
12
+ #include <react/renderer/animationbackend/AnimationChoreographer.h>
13
+
14
+ #include <atomic>
15
+
16
+ namespace basalt {
17
+
18
+ class GtkAnimationChoreographer final : public facebook::react::AnimationChoreographer {
19
+ public:
20
+ GtkAnimationChoreographer() = default;
21
+ ~GtkAnimationChoreographer() override;
22
+
23
+ // Called from the JS thread. Both marshal to the main thread before touching
24
+ // the frame clock.
25
+ void resume() override;
26
+ void pause() override;
27
+
28
+ // Called on the GTK main thread once the surface root is realised: a widget
29
+ // has no frame clock before realisation.
30
+ void attachToWidget(GtkWidget *widget);
31
+ void detach();
32
+
33
+ private:
34
+ static void onFrameClockUpdate(GdkFrameClock *clock, gpointer userData);
35
+ static gboolean syncRunningStateOnMainThread(gpointer userData);
36
+
37
+ // gdk_frame_clock_begin_updating/end_updating are refcounted, so calls must
38
+ // be balanced. This tracks whether our single reference is currently held.
39
+ void setUpdating(bool shouldUpdate);
40
+
41
+ GdkFrameClock *frameClock_{nullptr};
42
+ gulong updateHandler_{0};
43
+ bool updating_{false};
44
+
45
+ // Written from the JS thread, read on the main thread.
46
+ std::atomic<bool> running_{false};
47
+ };
48
+
49
+ } // namespace basalt
@@ -0,0 +1,269 @@
1
+ #include "GtkDropTarget.h"
2
+
3
+ #include "DragAndDrop.h"
4
+
5
+ #include <string>
6
+ #include <vector>
7
+
8
+ namespace basalt {
9
+ namespace {
10
+
11
+ // What the drop target is currently over, so that a leave can be reported for
12
+ // the view that was entered rather than for whatever is under the pointer when
13
+ // the drag ends -- by then it may be nothing.
14
+ facebook::react::Tag gCurrentTarget = 0;
15
+
16
+ // Turns GTK's dragged value into the shared payload.
17
+ //
18
+ // Two types are asked for, and both may arrive: G_TYPE_FILE for a file
19
+ // manager's drag and G_TYPE_STRING for selected text. A drag carrying neither
20
+ // is one this app has nothing to say about, which `wouldAccept` then refuses.
21
+ DragPayload payloadFrom(const GValue *value) {
22
+ DragPayload payload;
23
+ if (value == nullptr) {
24
+ return payload;
25
+ }
26
+ if (G_VALUE_HOLDS(value, GDK_TYPE_FILE_LIST)) {
27
+ auto *files = static_cast<GSList *>(g_value_get_boxed(value));
28
+ for (GSList *node = files; node != nullptr; node = node->next) {
29
+ auto *file = static_cast<GFile *>(node->data);
30
+ char *path = file != nullptr ? g_file_get_path(file) : nullptr;
31
+ if (path != nullptr) {
32
+ payload.files.emplace_back(path);
33
+ g_free(path);
34
+ }
35
+ }
36
+ } else if (G_VALUE_HOLDS(value, G_TYPE_FILE)) {
37
+ auto *file = static_cast<GFile *>(g_value_get_object(value));
38
+ char *path = file != nullptr ? g_file_get_path(file) : nullptr;
39
+ if (path != nullptr) {
40
+ payload.files.emplace_back(path);
41
+ g_free(path);
42
+ }
43
+ } else if (G_VALUE_HOLDS(value, G_TYPE_STRING)) {
44
+ const char *text = g_value_get_string(value);
45
+ if (text != nullptr) {
46
+ payload.text = text;
47
+ }
48
+ }
49
+ return payload;
50
+ }
51
+
52
+ // What a drag offers, before it is dropped.
53
+ //
54
+ // GTK will not hand over the value until the drop on most backends, so the
55
+ // question "would anything here take this?" is answered from the formats the
56
+ // drag advertises rather than from its contents. That is the same question the
57
+ // desktop is asking, which is why the cursor can answer it.
58
+ std::uint16_t offeredBy(GdkDrop *drop) {
59
+ std::uint16_t offered = DropAcceptsNone;
60
+ if (drop == nullptr) {
61
+ return offered;
62
+ }
63
+ GdkContentFormats *formats = gdk_drop_get_formats(drop);
64
+ if (formats == nullptr) {
65
+ return offered;
66
+ }
67
+ if (gdk_content_formats_contain_gtype(formats, GDK_TYPE_FILE_LIST) ||
68
+ gdk_content_formats_contain_gtype(formats, G_TYPE_FILE)) {
69
+ offered |= DropAcceptsFiles;
70
+ }
71
+ if (gdk_content_formats_contain_gtype(formats, G_TYPE_STRING)) {
72
+ offered |= DropAcceptsText;
73
+ }
74
+ return offered;
75
+ }
76
+
77
+ void reportLeaveIfAny(double x, double y) {
78
+ if (gCurrentTarget == 0) {
79
+ return;
80
+ }
81
+ DropEvent event;
82
+ event.tag = gCurrentTarget;
83
+ event.phase = DropPhase::Leave;
84
+ event.x = x;
85
+ event.y = y;
86
+ reportDrop(event);
87
+ gCurrentTarget = 0;
88
+ }
89
+
90
+ } // namespace
91
+
92
+ facebook::react::Tag dropTargetAt(RnView *root, double x, double y, std::uint16_t accepts) {
93
+ if (root == nullptr || !RN_IS_VIEW(root)) {
94
+ return 0;
95
+ }
96
+ GtkWidget *const rootWidget = GTK_WIDGET(root);
97
+ GtkWidget *widget = gtk_widget_pick(rootWidget, x, y, GTK_PICK_DEFAULT);
98
+
99
+ for (; widget != nullptr; widget = gtk_widget_get_parent(widget)) {
100
+ // The chain is not all RnViews -- a <TextInput>'s peer is a GtkText -- so
101
+ // anything else is stepped over rather than stopping the walk. Same rule
102
+ // as GtkTitleBarLayout.cpp.
103
+ if (RN_IS_VIEW(widget)) {
104
+ const char *id = rn_view_get_native_id(RN_VIEW(widget));
105
+ if (id != nullptr) {
106
+ const std::uint16_t wanted = dropAcceptsFrom(id);
107
+ if (wanted != DropAcceptsNone && (wanted & accepts) != 0) {
108
+ return rn_view_get_tag(RN_VIEW(widget));
109
+ }
110
+ }
111
+ }
112
+ if (widget == rootWidget) {
113
+ break;
114
+ }
115
+ }
116
+ return 0;
117
+ }
118
+
119
+ DragPayload dragPayloadAt(RnView *root, double x, double y) {
120
+ if (root == nullptr || !RN_IS_VIEW(root)) {
121
+ return {};
122
+ }
123
+ GtkWidget *const rootWidget = GTK_WIDGET(root);
124
+ GtkWidget *widget = gtk_widget_pick(rootWidget, x, y, GTK_PICK_DEFAULT);
125
+
126
+ // The same walk the drop side makes, and the same reason: a label inside a
127
+ // draggable view should be draggable without being marked itself.
128
+ for (; widget != nullptr; widget = gtk_widget_get_parent(widget)) {
129
+ if (RN_IS_VIEW(widget)) {
130
+ const char *id = rn_view_get_native_id(RN_VIEW(widget));
131
+ if (id != nullptr) {
132
+ const DragPayload payload = dragPayloadFrom(id);
133
+ if (!payload.empty()) {
134
+ return payload;
135
+ }
136
+ }
137
+ }
138
+ if (widget == rootWidget) {
139
+ break;
140
+ }
141
+ }
142
+ return {};
143
+ }
144
+
145
+ void attachDragSource(RnView *root) {
146
+ if (root == nullptr || !RN_IS_VIEW(root)) {
147
+ return;
148
+ }
149
+
150
+ GtkDragSource *source = gtk_drag_source_new();
151
+ gtk_drag_source_set_actions(source, GDK_ACTION_COPY);
152
+
153
+ // "prepare" is asked once, when the gesture has gone far enough to be a
154
+ // drag, and its answer *is* the drag: returning NULL means this point is not
155
+ // draggable and the gesture becomes an ordinary press again. Which is why
156
+ // the payload has to be known already -- there is nowhere to wait here.
157
+ g_signal_connect(source,
158
+ "prepare",
159
+ G_CALLBACK(+[](GtkDragSource *, double x, double y, gpointer data)
160
+ -> GdkContentProvider * {
161
+ auto *rootView = static_cast<RnView *>(data);
162
+ const DragPayload payload = dragPayloadAt(rootView, x, y);
163
+ if (payload.hasFiles()) {
164
+ GFile *file = g_file_new_for_path(payload.files.front().c_str());
165
+ GdkContentProvider *provider =
166
+ gdk_content_provider_new_typed(G_TYPE_FILE, file);
167
+ g_object_unref(file);
168
+ return provider;
169
+ }
170
+ if (payload.hasText()) {
171
+ return gdk_content_provider_new_typed(
172
+ G_TYPE_STRING, payload.text.c_str());
173
+ }
174
+ return nullptr;
175
+ }),
176
+ root);
177
+
178
+ gtk_widget_add_controller(GTK_WIDGET(root), GTK_EVENT_CONTROLLER(source));
179
+ }
180
+
181
+ void attachDropTarget(RnView *root) {
182
+ if (root == nullptr || !RN_IS_VIEW(root)) {
183
+ return;
184
+ }
185
+
186
+ // Both types, and COPY only: this platform has no notion of a drag that
187
+ // moves or links, and offering one it cannot honour would be a cursor that
188
+ // promises something the drop does not do.
189
+ GtkDropTarget *target = gtk_drop_target_new(G_TYPE_INVALID, GDK_ACTION_COPY);
190
+ GType types[] = {GDK_TYPE_FILE_LIST, G_TYPE_STRING};
191
+ gtk_drop_target_set_gtypes(target, types, G_N_ELEMENTS(types));
192
+
193
+ // "motion" rather than "enter": enter fires once for the *window*, and what
194
+ // an app wants to know is which of its views the pointer is over, which
195
+ // changes without ever leaving.
196
+ g_signal_connect(target,
197
+ "motion",
198
+ G_CALLBACK(+[](GtkDropTarget *self, double x, double y, gpointer data)
199
+ -> GdkDragAction {
200
+ auto *rootView = static_cast<RnView *>(data);
201
+ const std::uint16_t offered = offeredBy(gtk_drop_target_get_current_drop(self));
202
+ const facebook::react::Tag found = dropTargetAt(rootView, x, y, offered);
203
+
204
+ if (found != gCurrentTarget) {
205
+ reportLeaveIfAny(x, y);
206
+ }
207
+ if (found == 0) {
208
+ // Nothing here takes it: no action, which is what makes
209
+ // the cursor say so.
210
+ return static_cast<GdkDragAction>(0);
211
+ }
212
+ gCurrentTarget = found;
213
+
214
+ DropEvent event;
215
+ event.tag = found;
216
+ event.phase = DropPhase::Over;
217
+ event.x = x;
218
+ event.y = y;
219
+ reportDrop(event);
220
+ return GDK_ACTION_COPY;
221
+ }),
222
+ root);
223
+
224
+ g_signal_connect(target,
225
+ "leave",
226
+ G_CALLBACK(+[](GtkDropTarget *, gpointer) { reportLeaveIfAny(0.0, 0.0); }),
227
+ nullptr);
228
+
229
+ g_signal_connect(target,
230
+ "drop",
231
+ G_CALLBACK(+[](GtkDropTarget *,
232
+ const GValue *value,
233
+ double x,
234
+ double y,
235
+ gpointer data) -> gboolean {
236
+ auto *rootView = static_cast<RnView *>(data);
237
+ const DragPayload payload = payloadFrom(value);
238
+ // Asked again from the payload rather than from the
239
+ // formats: this is the one moment the contents are known,
240
+ // and a view that accepts only files should not be handed
241
+ // a drag that turned out to be only text.
242
+ std::uint16_t carries = DropAcceptsNone;
243
+ if (payload.hasFiles()) {
244
+ carries |= DropAcceptsFiles;
245
+ }
246
+ if (payload.hasText()) {
247
+ carries |= DropAcceptsText;
248
+ }
249
+ const facebook::react::Tag found = dropTargetAt(rootView, x, y, carries);
250
+ gCurrentTarget = 0;
251
+ if (found == 0) {
252
+ return FALSE;
253
+ }
254
+
255
+ DropEvent event;
256
+ event.tag = found;
257
+ event.phase = DropPhase::Drop;
258
+ event.x = x;
259
+ event.y = y;
260
+ event.payload = payload;
261
+ reportDrop(event);
262
+ return TRUE;
263
+ }),
264
+ root);
265
+
266
+ gtk_widget_add_controller(GTK_WIDGET(root), GTK_EVENT_CONTROLLER(target));
267
+ }
268
+
269
+ } // namespace basalt
@@ -0,0 +1,40 @@
1
+ // The GTK half of drag and drop: what happens when something is dragged onto
2
+ // the window.
3
+ //
4
+ // See core/DragAndDrop.h for the model, which is shared, and for why a view
5
+ // declares itself a drop target by its nativeID.
6
+
7
+ #pragma once
8
+
9
+ #include "DragAndDrop.h"
10
+ #include "RnView.h"
11
+
12
+ #include <gtk/gtk.h>
13
+
14
+ #include <cstdint>
15
+
16
+ #include <react/renderer/core/ReactPrimitives.h>
17
+
18
+ namespace basalt {
19
+
20
+ // The deepest view under this point that would take what is being dragged, or
21
+ // 0 for none.
22
+ //
23
+ // The same walk `isTitleBarDragRegionAt` makes, and it has to be: a label
24
+ // inside a drop target should not need marking, and a drop target inside
25
+ // another should win.
26
+ facebook::react::Tag dropTargetAt(RnView *root, double x, double y, std::uint16_t accepts);
27
+
28
+ // Attaches a GtkDropTarget to the surface root, so the window accepts files
29
+ // and text dragged onto it from anywhere on the desktop.
30
+ void attachDropTarget(RnView *root);
31
+
32
+ // The payload a marked view under this point represents, or an empty one.
33
+ // The other direction: see core/DragAndDrop.h.
34
+ DragPayload dragPayloadAt(RnView *root, double x, double y);
35
+
36
+ // Attaches a GtkDragSource, so a marked view can be dragged out of the window
37
+ // to anywhere on the desktop.
38
+ void attachDragSource(RnView *root);
39
+
40
+ } // namespace basalt