basalt-win32 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 (83) hide show
  1. package/LICENSE +21 -0
  2. package/cli/runWindows.js +35 -0
  3. package/cli/shared.js +75 -0
  4. package/native/CMakeLists.txt +398 -0
  5. package/native/tests/test_win32_accessibility.cpp +271 -0
  6. package/native/tests/test_win32_clipboard.cpp +121 -0
  7. package/native/tests/test_win32_focus.cpp +195 -0
  8. package/native/tests/test_win32_hittest.cpp +410 -0
  9. package/native/tests/test_win32_image.cpp +239 -0
  10. package/native/tests/test_win32_imageloader.cpp +208 -0
  11. package/native/tests/test_win32_input.cpp +361 -0
  12. package/native/tests/test_win32_key_events.cpp +84 -0
  13. package/native/tests/test_win32_mounting.cpp +410 -0
  14. package/native/tests/test_win32_paint.cpp +505 -0
  15. package/native/tests/test_win32_scrollview.cpp +385 -0
  16. package/native/tests/test_win32_text.cpp +258 -0
  17. package/native/tests/test_win32_textinput.cpp +609 -0
  18. package/native/tests/test_win32_titlebar.cpp +111 -0
  19. package/native/tests/test_win32_titlebar_actions.cpp +62 -0
  20. package/native/tests/test_win32_view.cpp +222 -0
  21. package/native/win32/ColorSchemeWin32.cpp +56 -0
  22. package/native/win32/ComponentRegistryWin32.cpp +124 -0
  23. package/native/win32/DirectWriteLayout.cpp +116 -0
  24. package/native/win32/DirectWriteLayout.h +36 -0
  25. package/native/win32/DirectWriteLayoutManager.cpp +124 -0
  26. package/native/win32/FontRegistryDirectWrite.cpp +117 -0
  27. package/native/win32/PlatformServicesWin32.cpp +643 -0
  28. package/native/win32/RnWin32Accessible.cpp +258 -0
  29. package/native/win32/RnWin32Accessible.h +91 -0
  30. package/native/win32/RnWin32Image.cpp +265 -0
  31. package/native/win32/RnWin32Image.h +93 -0
  32. package/native/win32/RnWin32TextLayout.cpp +310 -0
  33. package/native/win32/RnWin32TextLayout.h +160 -0
  34. package/native/win32/RnWin32View.cpp +1281 -0
  35. package/native/win32/RnWin32View.h +506 -0
  36. package/native/win32/Win32AnimationChoreographer.cpp +76 -0
  37. package/native/win32/Win32AnimationChoreographer.h +61 -0
  38. package/native/win32/Win32Clip.h +173 -0
  39. package/native/win32/Win32DropTarget.cpp +567 -0
  40. package/native/win32/Win32DropTarget.h +42 -0
  41. package/native/win32/Win32Focus.cpp +262 -0
  42. package/native/win32/Win32Focus.h +109 -0
  43. package/native/win32/Win32ImageLoader.cpp +167 -0
  44. package/native/win32/Win32ImageLoader.h +104 -0
  45. package/native/win32/Win32KeyEvents.cpp +126 -0
  46. package/native/win32/Win32KeyEvents.h +20 -0
  47. package/native/win32/Win32MenuBar.cpp +352 -0
  48. package/native/win32/Win32MenuBar.h +16 -0
  49. package/native/win32/Win32MountingManager.cpp +727 -0
  50. package/native/win32/Win32MountingManager.h +203 -0
  51. package/native/win32/Win32Packaging.cpp +161 -0
  52. package/native/win32/Win32Packaging.h +56 -0
  53. package/native/win32/Win32RunLoopObserver.cpp +48 -0
  54. package/native/win32/Win32RunLoopObserver.h +42 -0
  55. package/native/win32/Win32ScrollView.cpp +515 -0
  56. package/native/win32/Win32ScrollView.h +208 -0
  57. package/native/win32/Win32SkiaContext.cpp +221 -0
  58. package/native/win32/Win32SkiaContext.h +53 -0
  59. package/native/win32/Win32SkiaModule.cpp +80 -0
  60. package/native/win32/Win32SkiaModule.h +50 -0
  61. package/native/win32/Win32SkiaPeer.cpp +358 -0
  62. package/native/win32/Win32SkiaPeer.h +66 -0
  63. package/native/win32/Win32SkiaUnicode.cpp +42 -0
  64. package/native/win32/Win32Snapshot.cpp +201 -0
  65. package/native/win32/Win32Snapshot.h +66 -0
  66. package/native/win32/Win32Strings.h +54 -0
  67. package/native/win32/Win32TextInput.cpp +811 -0
  68. package/native/win32/Win32TextInput.h +232 -0
  69. package/native/win32/Win32TitleBar.cpp +562 -0
  70. package/native/win32/Win32TitleBar.h +146 -0
  71. package/native/win32/Win32TitleBarLayout.cpp +46 -0
  72. package/native/win32/Win32TitleBarLayout.h +58 -0
  73. package/native/win32/Win32TouchDispatcher.cpp +443 -0
  74. package/native/win32/Win32TouchDispatcher.h +153 -0
  75. package/native/win32/Win32UiThread.h +27 -0
  76. package/native/win32/Win32WindowControl.cpp +329 -0
  77. package/native/win32/Win32WindowModule.cpp +209 -0
  78. package/native/win32/Win32WindowModule.h +72 -0
  79. package/native/win32/demo_layout_win32.cpp +232 -0
  80. package/native/win32/main_win32.cpp +2481 -0
  81. package/native/win32/mount_harness_win32.cpp +316 -0
  82. package/package.json +26 -0
  83. package/react-native.config.js +22 -0
@@ -0,0 +1,46 @@
1
+ #include "Win32TitleBarLayout.h"
2
+
3
+ #include "RnWin32View.h"
4
+
5
+ #include <cmath>
6
+ #include <string>
7
+
8
+ namespace basalt::win32 {
9
+
10
+ bool isTitleBarDragRegionAt(RnWin32View *root, float x, float y) {
11
+ for (RnWin32View *view = hitTest(root, x, y); view != nullptr; view = view->parent()) {
12
+ const std::string &id = view->nativeId();
13
+ if (id == kTitleBarDragRegionId) {
14
+ return true;
15
+ }
16
+ if (id == kTitleBarNoDragRegionId) {
17
+ return false;
18
+ }
19
+ }
20
+ return false;
21
+ }
22
+
23
+ CaptionMetrics captionMetricsForDpi(uint32_t dpi) {
24
+ const float scale = dpi == 0 ? 1.0f : static_cast<float>(dpi) / 96.0f;
25
+ return CaptionMetrics{std::round(kCaptionHeightDips * scale),
26
+ std::round(kCaptionButtonWidthDips * scale)};
27
+ }
28
+
29
+ CaptionButton captionButtonAt(float x, float y, float clientWidth, const CaptionMetrics &metrics) {
30
+ if (y < 0 || y >= metrics.height || x < 0 || x >= clientWidth || metrics.buttonWidth <= 0) {
31
+ return CaptionButton::None;
32
+ }
33
+ const float fromRight = clientWidth - x;
34
+ if (fromRight <= metrics.buttonWidth) {
35
+ return CaptionButton::Close;
36
+ }
37
+ if (fromRight <= 2 * metrics.buttonWidth) {
38
+ return CaptionButton::Maximize;
39
+ }
40
+ if (fromRight <= 3 * metrics.buttonWidth) {
41
+ return CaptionButton::Minimize;
42
+ }
43
+ return CaptionButton::None;
44
+ }
45
+
46
+ } // namespace basalt::win32
@@ -0,0 +1,58 @@
1
+ // Where a hidden title bar's pieces are: which caption button a point is over,
2
+ // how big the caption is at a DPI, and whether a point drags the window.
3
+ //
4
+ // The geometry half of Win32TitleBar, kept apart from it so that it needs no
5
+ // window, no DWM and no React Native -- only a view tree and a size -- and can
6
+ // be tested with none of them. A custom caption fails in ways a person notices
7
+ // at once and a test does not, unless the test asks exactly these questions.
8
+
9
+ #pragma once
10
+
11
+ #include "TitleBarRegions.h"
12
+
13
+ #include <cstdint>
14
+
15
+ namespace basalt::win32 {
16
+
17
+ class RnWin32View;
18
+
19
+ // What <TitleBar.DragRegion> and <TitleBar.NoDragRegion> set as a view's
20
+ // nativeID, under this namespace's own names so that everything already
21
+ // written against them keeps compiling.
22
+ //
23
+ // The values come from core/TitleBarRegions.h rather than being spelled again
24
+ // here: GTK reads the same two strings, and src/TitleBar.tsx writes them, so a
25
+ // literal in this file would be the third copy of something that only works
26
+ // when all of them agree.
27
+ inline constexpr const char *kTitleBarDragRegionId = basalt::kTitleBarDragRegionId;
28
+ inline constexpr const char *kTitleBarNoDragRegionId = basalt::kTitleBarNoDragRegionId;
29
+
30
+ // Whether a point, in the root's coordinates, drags the window.
31
+ //
32
+ // The deepest view under the point and then each of its ancestors are asked in
33
+ // turn, and the nearest one marked either way decides. So a button inside a
34
+ // drag region stays a button once it is marked no-drag, a label inside a drag
35
+ // region drags without being marked at all, and a point under nothing marked
36
+ // is ordinary client area.
37
+ bool isTitleBarDragRegionAt(RnWin32View *root, float x, float y);
38
+
39
+ enum class CaptionButton { None, Minimize, Maximize, Close };
40
+
41
+ // Windows 11's own caption sizes, in device-independent pixels.
42
+ inline constexpr float kCaptionHeightDips = 32.0f;
43
+ inline constexpr float kCaptionButtonWidthDips = 46.0f;
44
+
45
+ // Those sizes at a DPI, in client pixels -- which on this host are also the
46
+ // surface root's units, because the root is sized to the client rectangle.
47
+ struct CaptionMetrics {
48
+ float height{0};
49
+ float buttonWidth{0};
50
+ };
51
+ CaptionMetrics captionMetricsForDpi(uint32_t dpi);
52
+
53
+ // The caption button under a point in client coordinates, for a window
54
+ // `clientWidth` wide. The three sit flush right, in the order Windows puts
55
+ // them: minimise, maximise, close.
56
+ CaptionButton captionButtonAt(float x, float y, float clientWidth, const CaptionMetrics &metrics);
57
+
58
+ } // namespace basalt::win32
@@ -0,0 +1,443 @@
1
+ #include "Win32TouchDispatcher.h"
2
+
3
+ #include "Gestures.h"
4
+
5
+ #include <react/renderer/components/view/PointerEvent.h>
6
+ #include <react/renderer/components/view/TouchEvent.h>
7
+
8
+ #include <cstdint>
9
+
10
+ namespace basalt {
11
+
12
+ using facebook::react::HighResTimeStamp;
13
+ using facebook::react::Point;
14
+ using facebook::react::PointerEvent;
15
+ using facebook::react::Tag;
16
+ using facebook::react::Touch;
17
+ using facebook::react::TouchEvent;
18
+ using facebook::react::TouchEventEmitter;
19
+ using facebook::react::Touches;
20
+ using win32::RnWin32View;
21
+
22
+ namespace {
23
+
24
+ // A desktop pointer is one touch point, and React Native identifies touches by
25
+ // number. Zero is the first (and here only) finger. The same number identifies
26
+ // the pointer in pointer events, where React Native keys its hover tracking by
27
+ // it.
28
+ constexpr int kPointerIdentifier = 0;
29
+
30
+ // The payload for a hovering pointer, at a point in root coordinates with the
31
+ // target's origin in the same space.
32
+ PointerEvent hoverEvent(double x, double y, double originX, double originY) {
33
+ PointerEvent event{};
34
+ event.pointerId = kPointerIdentifier;
35
+ event.pointerType = "mouse";
36
+ event.clientPoint = Point{.x = static_cast<facebook::react::Float>(x),
37
+ .y = static_cast<facebook::react::Float>(y)};
38
+ event.screenPoint = event.clientPoint;
39
+ event.offsetPoint = Point{.x = static_cast<facebook::react::Float>(x - originX),
40
+ .y = static_cast<facebook::react::Float>(y - originY)};
41
+ event.width = 1;
42
+ event.height = 1;
43
+ // A hovering mouse presses nothing. `button` is -1 rather than 0 because 0 is
44
+ // the left button; -1 is W3C's "no button changed state".
45
+ event.button = -1;
46
+ event.buttons = 0;
47
+ event.isPrimary = true;
48
+ event.timeStamp = HighResTimeStamp::now();
49
+ return event;
50
+ }
51
+
52
+ // A point through a 2D affine matrix in Direct2D's Matrix3x2F order and its
53
+ // row-vector convention, which is the order `localToParent` writes.
54
+ void mapPoint(const float m[6], double x, double y, double &outX, double &outY) {
55
+ const double mappedX = m[0] * x + m[2] * y + m[4];
56
+ const double mappedY = m[1] * x + m[3] * y + m[5];
57
+ outX = mappedX;
58
+ outY = mappedY;
59
+ }
60
+
61
+ // The views under a point, innermost first, each with its origin in the
62
+ // surface root's coordinates. That is what a gesture recogniser needs and a
63
+ // touch does not: a touch is reported against one target, while a gesture may
64
+ // be attached to any ancestor of the view that was hit.
65
+ //
66
+ // The origin is carried *up* the chain rather than recomputed per view, so the
67
+ // walk stays linear and each view's contribution -- its own transform, then its
68
+ // parent's scroll offset -- is applied exactly once. AppKit gets the same
69
+ // answer from convertPoint:toView: and GTK from the widget's allocation; here
70
+ // there is no toolkit geometry, so the composition is written out. It is the
71
+ // mirror of what `hitTest` did on the way down, which is the only thing that
72
+ // makes the two agree about a rotated or scrolled ancestor.
73
+ //
74
+ // Built only when something is attached; see core/Gestures.h.
75
+ std::vector<basalt::HitView> hitChain(RnWin32View *root, double x, double y) {
76
+ std::vector<basalt::HitView> chain;
77
+ RnWin32View *hit = win32::hitTest(root, static_cast<float>(x), static_cast<float>(y));
78
+ if (hit == nullptr) {
79
+ return chain;
80
+ }
81
+
82
+ double originX = 0.0;
83
+ double originY = 0.0;
84
+ for (RnWin32View *view = hit; view != nullptr; view = view->parent()) {
85
+ chain.push_back(basalt::HitView{
86
+ .tag = static_cast<int>(view->tag()), .originX = originX, .originY = originY});
87
+ if (view == root) {
88
+ break;
89
+ }
90
+ RnWin32View *parent = view->parent();
91
+ if (parent == nullptr) {
92
+ break;
93
+ }
94
+ float local[6];
95
+ view->localToParent(local);
96
+ mapPoint(local, originX, originY, originX, originY);
97
+ originX -= parent->scrollX();
98
+ originY -= parent->scrollY();
99
+ }
100
+ return chain;
101
+ }
102
+
103
+ } // namespace
104
+
105
+ Win32TouchDispatcher::Win32TouchDispatcher(
106
+ Win32MountingManager *mountingManager,
107
+ RnWin32View *surfaceRoot)
108
+ : mountingManager_(mountingManager), surfaceRoot_(surfaceRoot) {}
109
+
110
+ void Win32TouchDispatcher::synthesiseTap(double x, double y) {
111
+ synthesiseTap(x, y, basalt::PointerButton::Primary);
112
+ }
113
+
114
+ void Win32TouchDispatcher::synthesiseTap(double x, double y, basalt::PointerButton button) {
115
+ dispatchTouchStart(x, y, button);
116
+ dispatchTouchEnd(x, y, button);
117
+ }
118
+
119
+ void Win32TouchDispatcher::synthesiseHover(double x, double y) {
120
+ if (x < 0 || y < 0) {
121
+ dispatchHoverLeave();
122
+ return;
123
+ }
124
+ dispatchHover(x, y);
125
+ }
126
+
127
+ void Win32TouchDispatcher::synthesiseDrag(
128
+ double fromX, double fromY, double toX, double toY, int steps) {
129
+ dispatchTouchStart(fromX, fromY, basalt::PointerButton::Primary);
130
+ const int count = steps < 1 ? 1 : steps;
131
+ for (int step = 1; step <= count; step++) {
132
+ const double progress = static_cast<double>(step) / count;
133
+ dispatchTouchMove(fromX + (toX - fromX) * progress, fromY + (toY - fromY) * progress);
134
+ }
135
+ dispatchTouchEnd(toX, toY, basalt::PointerButton::Primary);
136
+ }
137
+
138
+ // ---------------------------------------------------------------------------
139
+ // Hit testing
140
+ // ---------------------------------------------------------------------------
141
+
142
+ Tag hitTestTag(RnWin32View *root, double x, double y) {
143
+ RnWin32View *hit = win32::hitTest(root, static_cast<float>(x), static_cast<float>(y));
144
+ return hit == nullptr ? 0 : static_cast<Tag>(hit->tag());
145
+ }
146
+
147
+ // ---------------------------------------------------------------------------
148
+ // Dispatch
149
+ // ---------------------------------------------------------------------------
150
+
151
+ void Win32TouchDispatcher::dispatchTouchStart(double x, double y, basalt::PointerButton button) {
152
+ if (surfaceRoot_ == nullptr) {
153
+ return;
154
+ }
155
+
156
+ // The innermost view, and where inside it the press landed. Needed for the
157
+ // pointer event whichever button it was; PointerEventsProcessor walks up from
158
+ // there itself.
159
+ Tag target = 0;
160
+ double originX = 0;
161
+ double originY = 0;
162
+ for (const auto &view : hitChain(surfaceRoot_, x, y)) {
163
+ target = static_cast<Tag>(view.tag);
164
+ originX = view.originX;
165
+ originY = view.originY;
166
+ break;
167
+ }
168
+ if (target == 0) {
169
+ return;
170
+ }
171
+
172
+ // Every button produces a pointer event, which is where `button` can be said
173
+ // at all.
174
+ emitPointerButton(true, target, originX, originY, x, y, button);
175
+
176
+ // Only the primary one goes any further. A secondary click is not a press --
177
+ // the web fires no `click` for one and no desktop treats it as an activation
178
+ // -- so it must not reach the responder system, a gesture handler, or
179
+ // `onPress`. See core/PointerButtons.h.
180
+ //
181
+ // This host was the odd one out in the other direction: it handled only
182
+ // WM_LBUTTONDOWN, so a right-click did nothing whatsoever.
183
+ if (!basalt::isPressButton(button)) {
184
+ return;
185
+ }
186
+
187
+ if (!basalt::gestures().empty()) {
188
+ basalt::gestures().pointerDown(
189
+ hitChain(surfaceRoot_, x, y), x, y, basalt::monotonicMilliseconds());
190
+ }
191
+
192
+ activeTarget_ = target;
193
+ isDown_ = true;
194
+ emit(TouchKind::Start, target, x, y);
195
+
196
+ // A gesture that claimed the pointer on contact -- a native handler -- takes
197
+ // it away from React Native's responder system immediately.
198
+ yieldToGesture(x, y);
199
+ }
200
+
201
+ void Win32TouchDispatcher::dispatchTouchMove(double x, double y) {
202
+ if (!basalt::gestures().empty()) {
203
+ basalt::gestures().pointerMove(x, y, basalt::monotonicMilliseconds());
204
+ }
205
+
206
+ // Motion with no button down is hover, which the touch model has no place
207
+ // for. Reporting it would look to the responder system like a finger dragging
208
+ // across the screen at all times.
209
+ if (!isDown_ || activeTarget_ == 0) {
210
+ return;
211
+ }
212
+ if (yieldToGesture(x, y)) {
213
+ return;
214
+ }
215
+ emit(TouchKind::Move, activeTarget_, x, y);
216
+ }
217
+
218
+ void Win32TouchDispatcher::dispatchTouchEnd(double x, double y, basalt::PointerButton button) {
219
+ // The release half of the pointer event, for every button. Reported against
220
+ // whatever is under the pointer now rather than what was under it on press,
221
+ // which is what a release means when nothing was captured.
222
+ if (surfaceRoot_ != nullptr) {
223
+ for (const auto &view : hitChain(surfaceRoot_, x, y)) {
224
+ emitPointerButton(
225
+ false, static_cast<Tag>(view.tag), view.originX, view.originY, x, y, button);
226
+ break;
227
+ }
228
+ }
229
+
230
+ // And nothing else for a button that never pressed anything -- there is no
231
+ // touch to end, no gesture to finish and no <Switch> to toggle.
232
+ if (!basalt::isPressButton(button)) {
233
+ return;
234
+ }
235
+
236
+ if (!basalt::gestures().empty()) {
237
+ basalt::gestures().pointerUp(x, y, basalt::monotonicMilliseconds());
238
+ }
239
+
240
+ if (!isDown_ || activeTarget_ == 0) {
241
+ return;
242
+ }
243
+ const Tag target = activeTarget_;
244
+ isDown_ = false;
245
+ activeTarget_ = 0;
246
+ emit(TouchKind::End, target, x, y);
247
+
248
+ // A <Switch> on this host is painted rather than mounted, so nothing
249
+ // underneath it turns a click into a toggle -- GTK's GtkSwitch and AppKit's
250
+ // NSSwitch both do that for themselves. This is the one line that makes up
251
+ // the difference; it is a no-op for every view that is not a switch.
252
+ if (mountingManager_ != nullptr) {
253
+ mountingManager_->pressedView(target);
254
+ }
255
+ }
256
+
257
+ void Win32TouchDispatcher::dispatchTouchCancel() {
258
+ if (!basalt::gestures().empty()) {
259
+ basalt::gestures().pointerCancel();
260
+ }
261
+
262
+ if (!isDown_ || activeTarget_ == 0) {
263
+ return;
264
+ }
265
+ const Tag target = activeTarget_;
266
+ isDown_ = false;
267
+ activeTarget_ = 0;
268
+ emit(TouchKind::Cancel, target, 0, 0);
269
+ }
270
+
271
+ // ---------------------------------------------------------------------------
272
+ // Hover
273
+ // ---------------------------------------------------------------------------
274
+
275
+ void Win32TouchDispatcher::dispatchHover(double x, double y) {
276
+ // The same chain the gesture path uses -- this is the one place where the
277
+ // absence of a toolkit pays off, because `hitChain` already composes the
278
+ // transforms and scroll offsets that GTK and AppKit answer for themselves.
279
+ //
280
+ // Only the innermost view is reported against -- React Native's
281
+ // PointerEventsProcessor walks up from it itself -- but the whole chain is
282
+ // still needed, because a listener on any ancestor is reason to dispatch.
283
+ Tag target = 0;
284
+ double originX = 0;
285
+ double originY = 0;
286
+ std::uint16_t listeners = HoverListenerNone;
287
+ for (const auto &view : hitChain(surfaceRoot_, x, y)) {
288
+ if (target == 0) {
289
+ target = static_cast<Tag>(view.tag);
290
+ originX = view.originX;
291
+ originY = view.originY;
292
+ }
293
+ listeners |= mountingManager_->hoverListenersForTag(static_cast<Tag>(view.tag));
294
+ }
295
+
296
+ if (target == 0) {
297
+ dispatchHoverLeave();
298
+ return;
299
+ }
300
+ if (!hover_.admitMove(static_cast<int>(target), listeners)) {
301
+ return;
302
+ }
303
+ emitPointerMove(target, originX, originY, x, y);
304
+ }
305
+
306
+ void Win32TouchDispatcher::dispatchHoverLeave() {
307
+ const int target = hover_.admitLeave();
308
+ if (target == 0) {
309
+ return;
310
+ }
311
+ const auto emitter = std::dynamic_pointer_cast<const TouchEventEmitter>(
312
+ mountingManager_->eventEmitterForTag(static_cast<Tag>(target)));
313
+ if (emitter == nullptr) {
314
+ return;
315
+ }
316
+ // A leave from a platform is not forwarded: the processor reads it as the
317
+ // pointer being gone and unwinds the path it is holding. So the coordinates
318
+ // on it are never seen by an app, which is just as well -- WM_MOUSELEAVE
319
+ // reports no position at all.
320
+ emitter->onPointerLeave(hoverEvent(0, 0, 0, 0));
321
+ }
322
+
323
+ void Win32TouchDispatcher::emitPointerButton(bool down,
324
+ Tag target,
325
+ double originX,
326
+ double originY,
327
+ double x,
328
+ double y,
329
+ basalt::PointerButton button) {
330
+ const auto emitter = std::dynamic_pointer_cast<const TouchEventEmitter>(
331
+ mountingManager_->eventEmitterForTag(target));
332
+ if (emitter == nullptr) {
333
+ return;
334
+ }
335
+ PointerEvent event = hoverEvent(x, y, originX, originY);
336
+ event.button = static_cast<int>(button);
337
+ // Held *during* the event, which is a different number from `button` in the
338
+ // same event: on release nothing is held any more. See core/PointerButtons.h.
339
+ event.buttons = down ? static_cast<int>(basalt::buttonsMaskFor(button)) : 0;
340
+ if (down) {
341
+ emitter->onPointerDown(event);
342
+ } else {
343
+ emitter->onPointerUp(event);
344
+ }
345
+ }
346
+
347
+ void Win32TouchDispatcher::emitPointerMove(
348
+ Tag target, double originX, double originY, double x, double y) {
349
+ const auto emitter = std::dynamic_pointer_cast<const TouchEventEmitter>(
350
+ mountingManager_->eventEmitterForTag(target));
351
+ if (emitter == nullptr) {
352
+ return;
353
+ }
354
+ emitter->onPointerMove(hoverEvent(x, y, originX, originY));
355
+ }
356
+
357
+ // A gesture recogniser that has activated owns the pointer, and React Native's
358
+ // responder system must be told the touch it was following is gone -- otherwise
359
+ // panning across a <Pressable> pans *and* presses it. This is what RNGH's
360
+ // setJSResponder does on the platforms it was written for; here both sides are
361
+ // fed from this one place, so it is a cancel rather than a negotiation.
362
+ bool Win32TouchDispatcher::yieldToGesture(double x, double y) {
363
+ if (!isDown_ || activeTarget_ == 0 || !basalt::gestures().hasActiveHandler()) {
364
+ return false;
365
+ }
366
+ const Tag target = activeTarget_;
367
+ isDown_ = false;
368
+ activeTarget_ = 0;
369
+ emit(TouchKind::Cancel, target, x, y);
370
+ return true;
371
+ }
372
+
373
+ void Win32TouchDispatcher::emit(TouchKind kind, Tag target, double x, double y) {
374
+ if (mountingManager_ == nullptr) {
375
+ return;
376
+ }
377
+ const auto emitter = std::dynamic_pointer_cast<const TouchEventEmitter>(
378
+ mountingManager_->eventEmitterForTag(target));
379
+ if (emitter == nullptr) {
380
+ return;
381
+ }
382
+
383
+ const auto now = HighResTimeStamp::now();
384
+
385
+ Touch touch{};
386
+ touch.identifier = kPointerIdentifier;
387
+ touch.target = target;
388
+ // Coordinates arrive relative to the surface root, which is what React Native
389
+ // calls the page.
390
+ touch.pagePoint = Point{.x = static_cast<facebook::react::Float>(x),
391
+ .y = static_cast<facebook::react::Float>(y)};
392
+ touch.screenPoint = touch.pagePoint;
393
+ // Relative to the target, which is what `offsetPoint` means and what
394
+ // `locationX`/`locationY` are built from. It carried the page point until
395
+ // now: right only for a view at the surface's origin, and wrong by that
396
+ // view's position for every other.
397
+ //
398
+ // The page point is the fallback, which is what every touch carried before:
399
+ // the target unmounted between the press and this event, or a chain with no
400
+ // inverse. Refusing would drop the touch.
401
+ float offsetX = static_cast<float>(x);
402
+ float offsetY = static_cast<float>(y);
403
+ if (mountingManager_ != nullptr) {
404
+ if (RnWin32View *view = mountingManager_->viewForTag(target)) {
405
+ view->pageToLocal(surfaceRoot_, static_cast<float>(x), static_cast<float>(y),
406
+ offsetX, offsetY);
407
+ }
408
+ }
409
+ touch.offsetPoint = Point{.x = static_cast<facebook::react::Float>(offsetX),
410
+ .y = static_cast<facebook::react::Float>(offsetY)};
411
+ touch.force = 1.0F;
412
+ touch.timeStamp = now;
413
+
414
+ Touches changed{};
415
+ changed.insert(touch);
416
+
417
+ const bool isEnd = kind == TouchKind::End || kind == TouchKind::Cancel;
418
+
419
+ TouchEvent event{};
420
+ event.changedTouches = changed;
421
+ // On touchend the finger is gone, so it is no longer in `touches`. Getting
422
+ // this wrong leaves the responder system believing a touch is still active,
423
+ // which swallows the next press.
424
+ event.touches = isEnd ? Touches{} : changed;
425
+ event.targetTouches = event.touches;
426
+
427
+ switch (kind) {
428
+ case TouchKind::Start:
429
+ emitter->onTouchStart(event);
430
+ break;
431
+ case TouchKind::Move:
432
+ emitter->onTouchMove(event);
433
+ break;
434
+ case TouchKind::End:
435
+ emitter->onTouchEnd(event);
436
+ break;
437
+ case TouchKind::Cancel:
438
+ emitter->onTouchCancel(event);
439
+ break;
440
+ }
441
+ }
442
+
443
+ } // namespace basalt