basalt-core 0.0.0-rc5 → 0.1.0

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 (143) hide show
  1. package/dist/cli/basalt.d.ts.map +1 -1
  2. package/dist/cli/basalt.js +18 -1
  3. package/dist/cli/basalt.js.map +1 -1
  4. package/dist/cli/bundle-cli.js +0 -0
  5. package/dist/cli/packageApp.d.ts.map +1 -1
  6. package/dist/cli/packageApp.js +7 -1
  7. package/dist/cli/packageApp.js.map +1 -1
  8. package/dist/metro-config.d.ts.map +1 -1
  9. package/dist/metro-config.js +53 -5
  10. package/dist/metro-config.js.map +1 -1
  11. package/dist/src/KeyHandler.d.ts +1 -1
  12. package/dist/src/KeyHandler.d.ts.map +1 -1
  13. package/dist/src/KeyHandler.js.map +1 -1
  14. package/dist/src/index.d.ts +5 -0
  15. package/dist/src/index.d.ts.map +1 -1
  16. package/dist/src/overrides/Alert.d.ts +1 -1
  17. package/dist/src/overrides/Alert.js +1 -1
  18. package/dist/src/overrides/BaseViewConfig.d.ts +10 -3
  19. package/dist/src/overrides/BaseViewConfig.d.ts.map +1 -1
  20. package/dist/src/overrides/BaseViewConfig.js +21 -3
  21. package/dist/src/overrides/BaseViewConfig.js.map +1 -1
  22. package/dist/src/overrides/Button.d.ts +81 -0
  23. package/dist/src/overrides/Button.d.ts.map +1 -0
  24. package/dist/src/overrides/Button.js +109 -0
  25. package/dist/src/overrides/Button.js.map +1 -0
  26. package/dist/src/overrides/ScrollView.d.ts +25 -2
  27. package/dist/src/overrides/ScrollView.d.ts.map +1 -1
  28. package/dist/src/overrides/ScrollView.js +64 -7
  29. package/dist/src/overrides/ScrollView.js.map +1 -1
  30. package/dist/src/overrides/Settings.d.ts +81 -0
  31. package/dist/src/overrides/Settings.d.ts.map +1 -0
  32. package/dist/src/overrides/Settings.js +120 -0
  33. package/dist/src/overrides/Settings.js.map +1 -0
  34. package/dist/src/overrides/processDecelerationRate.d.ts +20 -0
  35. package/dist/src/overrides/processDecelerationRate.d.ts.map +1 -0
  36. package/dist/src/overrides/processDecelerationRate.js +31 -0
  37. package/dist/src/overrides/processDecelerationRate.js.map +1 -0
  38. package/dist/src/useContextMenu.d.ts +57 -6
  39. package/dist/src/useContextMenu.d.ts.map +1 -1
  40. package/dist/src/useContextMenu.js +34 -1
  41. package/dist/src/useContextMenu.js.map +1 -1
  42. package/dist/tsconfig.tsbuildinfo +1 -1
  43. package/native/CMakeLists.txt +51 -0
  44. package/native/core/ActionSheet.cpp +75 -0
  45. package/native/core/ActionSheet.h +90 -0
  46. package/native/core/AppStateHost.cpp +76 -0
  47. package/native/core/AppStateHost.h +68 -0
  48. package/native/core/BackgroundLayers.h +282 -0
  49. package/native/core/BlendModes.h +67 -0
  50. package/native/core/CoreModules.cpp +380 -11
  51. package/native/core/CoreModules.h +140 -4
  52. package/native/core/CrashHandler.cpp +246 -0
  53. package/native/core/CrashHandler.h +65 -0
  54. package/native/core/CursorNames.h +110 -0
  55. package/native/core/DevMenu.cpp +6 -5
  56. package/native/core/DeviceMetrics.cpp +87 -0
  57. package/native/core/DeviceMetrics.h +80 -0
  58. package/native/core/Filters.h +336 -0
  59. package/native/core/FontFitting.h +183 -0
  60. package/native/core/FontScaling.h +153 -0
  61. package/native/core/FontVariants.h +148 -0
  62. package/native/core/Gradients.h +523 -0
  63. package/native/core/ImageAnimation.h +109 -0
  64. package/native/core/ImageBytes.cpp +28 -1
  65. package/native/core/ImageBytes.h +17 -1
  66. package/native/core/ImageProgress.h +85 -0
  67. package/native/core/LabelRegistry.h +189 -0
  68. package/native/core/LegacyShadow.h +90 -0
  69. package/native/core/LiveRegions.h +114 -0
  70. package/native/core/MenuModule.cpp +35 -4
  71. package/native/core/MountingWalk.h +125 -2
  72. package/native/core/PlatformServices.h +187 -3
  73. package/native/core/SafeAreaComponent.cpp +115 -0
  74. package/native/core/SafeAreaComponent.h +105 -0
  75. package/native/core/ScreensComponent.cpp +201 -0
  76. package/native/core/ScreensComponent.h +216 -0
  77. package/native/core/ScrollBounds.cpp +8 -0
  78. package/native/core/ScrollBounds.h +14 -0
  79. package/native/core/ScrollIndicator.h +67 -0
  80. package/native/core/ScrollMomentum.cpp +24 -0
  81. package/native/core/ScrollMomentum.h +14 -0
  82. package/native/core/ScrollSnap.cpp +121 -12
  83. package/native/core/ScrollSnap.h +45 -0
  84. package/native/core/ScrollVisiblePosition.cpp +68 -0
  85. package/native/core/ScrollVisiblePosition.h +101 -0
  86. package/native/core/SettingsStore.cpp +199 -0
  87. package/native/core/SettingsStore.h +127 -0
  88. package/native/core/ShadowShape.h +38 -0
  89. package/native/core/SystemLocale.cpp +103 -0
  90. package/native/core/SystemLocale.h +86 -0
  91. package/native/core/TestDialog.cpp +45 -6
  92. package/native/core/TestDialog.h +15 -0
  93. package/native/core/TextAlignments.h +240 -0
  94. package/native/core/TextChecking.h +133 -0
  95. package/native/core/TextColors.h +83 -0
  96. package/native/core/TextDecorations.h +101 -0
  97. package/native/core/TextDirection.cpp +242 -0
  98. package/native/core/TextDirection.h +88 -0
  99. package/native/core/TextHighlight.h +30 -0
  100. package/native/core/TextSelection.cpp +97 -0
  101. package/native/core/TextSelection.h +161 -0
  102. package/native/core/TextShadows.h +96 -0
  103. package/native/core/TextVerticalAlign.h +33 -0
  104. package/native/core/UIManagerAccess.cpp +36 -0
  105. package/native/core/UIManagerAccess.h +28 -0
  106. package/native/core/WindowBoundsCache.cpp +58 -1
  107. package/native/core/WindowControl.h +24 -0
  108. package/native/core/WritingDirections.h +52 -0
  109. package/native/core/portability_probe.cpp +7 -0
  110. package/native/tests/RenamedFontFile.h +161 -0
  111. package/native/tests/TestHarness.cpp +18 -0
  112. package/native/tests/test_action_sheet.cpp +157 -0
  113. package/native/tests/test_app_state.cpp +60 -0
  114. package/native/tests/test_background_layers.cpp +281 -0
  115. package/native/tests/test_blend_modes.cpp +79 -0
  116. package/native/tests/test_blobs.cpp +1 -1
  117. package/native/tests/test_device_metrics.cpp +126 -0
  118. package/native/tests/test_filters.cpp +271 -0
  119. package/native/tests/test_font_fitting.cpp +172 -0
  120. package/native/tests/test_font_scaling.cpp +127 -0
  121. package/native/tests/test_font_variants.cpp +84 -0
  122. package/native/tests/test_gradients.cpp +390 -0
  123. package/native/tests/test_image_animation.cpp +105 -0
  124. package/native/tests/test_image_progress.cpp +94 -0
  125. package/native/tests/test_label_registry.cpp +262 -0
  126. package/native/tests/test_legacy_shadow.cpp +165 -0
  127. package/native/tests/test_live_regions.cpp +108 -0
  128. package/native/tests/test_menu_entries.cpp +192 -0
  129. package/native/tests/test_momentum.cpp +45 -0
  130. package/native/tests/test_scroll_bounds.cpp +42 -0
  131. package/native/tests/test_scroll_indicator.cpp +61 -0
  132. package/native/tests/test_scroll_snap.cpp +129 -0
  133. package/native/tests/test_scroll_visible_position.cpp +121 -0
  134. package/native/tests/test_settings_store.cpp +257 -0
  135. package/native/tests/test_shadow_shape.cpp +47 -0
  136. package/native/tests/test_system_locale.cpp +36 -0
  137. package/native/tests/test_text_alignment.cpp +299 -0
  138. package/native/tests/test_text_checking.cpp +31 -0
  139. package/native/tests/test_text_colors.cpp +92 -0
  140. package/native/tests/test_text_decorations.cpp +100 -0
  141. package/native/tests/test_text_selection.cpp +168 -0
  142. package/native/tests/test_text_shadows.cpp +132 -0
  143. package/package.json +4 -2
@@ -0,0 +1,246 @@
1
+ #include "CrashHandler.h"
2
+
3
+ #include <cstddef>
4
+ #include <cstdint>
5
+ #include <cstdlib>
6
+
7
+ #if defined(_WIN32)
8
+ #include <windows.h>
9
+ #else
10
+ #include <csignal>
11
+ #include <execinfo.h>
12
+ #include <pthread.h>
13
+ #include <unistd.h>
14
+ #endif
15
+
16
+ namespace basalt {
17
+
18
+ namespace {
19
+
20
+ #if !defined(_WIN32)
21
+
22
+ constexpr int kFatalSignals[] = {SIGSEGV, SIGBUS, SIGILL, SIGFPE, SIGABRT};
23
+ constexpr std::size_t kFatalSignalCount = sizeof(kFatalSignals) / sizeof(kFatalSignals[0]);
24
+
25
+ // What was installed before, so each handler can put it back and re-raise. An
26
+ // empty slot is one this never touched.
27
+ struct sigaction gPrevious[kFatalSignalCount];
28
+
29
+ // The thread that installed the handler, which is the main thread: the handler
30
+ // runs on whichever thread took the signal, and for a hang that is the first
31
+ // thing a reader needs to know. A backtrace that cannot be told apart from the
32
+ // JS thread's is half a diagnosis, which cost a round trip here once already.
33
+ // Recorded rather than derived, because `pthread_main_np` is not portable and
34
+ // `gettid`-style checks say nothing on a host whose main thread is not tid 1.
35
+ pthread_t gMainThread = {};
36
+ bool gMainThreadKnown = false;
37
+
38
+ int slotFor(int signalNumber) {
39
+ for (std::size_t i = 0; i < kFatalSignalCount; i++) {
40
+ if (kFatalSignals[i] == signalNumber) {
41
+ return static_cast<int>(i);
42
+ }
43
+ }
44
+ return -1;
45
+ }
46
+
47
+ // Everything below runs in a signal handler, so everything below is
48
+ // async-signal-safe. No allocation, no locks, no formatting.
49
+
50
+ void writeText(const char *text) {
51
+ if (text == nullptr) {
52
+ return;
53
+ }
54
+ std::size_t length = 0;
55
+ while (text[length] != '\0') {
56
+ length++;
57
+ }
58
+ // The result is deliberately ignored: there is nothing useful to do about a
59
+ // failed write from here, and a partial one still says more than nothing.
60
+ const ssize_t written = ::write(STDERR_FILENO, text, length);
61
+ (void)written;
62
+ }
63
+
64
+ void writeHex(std::uintptr_t value) {
65
+ char digits[] = "0123456789abcdef";
66
+ // 16 nibbles, a "0x", and the terminator.
67
+ char buffer[19];
68
+ buffer[0] = '0';
69
+ buffer[1] = 'x';
70
+ std::size_t at = 2;
71
+ bool leading = true;
72
+ for (int shift = 60; shift >= 0; shift -= 4) {
73
+ const unsigned nibble = static_cast<unsigned>((value >> shift) & 0xF);
74
+ if (nibble == 0 && leading && shift != 0) {
75
+ continue;
76
+ }
77
+ leading = false;
78
+ buffer[at++] = digits[nibble];
79
+ }
80
+ buffer[at] = '\0';
81
+ writeText(buffer);
82
+ }
83
+
84
+ const char *nameFor(int signalNumber) {
85
+ switch (signalNumber) {
86
+ case SIGSEGV:
87
+ return "SIGSEGV";
88
+ case SIGBUS:
89
+ return "SIGBUS";
90
+ case SIGILL:
91
+ return "SIGILL";
92
+ case SIGFPE:
93
+ return "SIGFPE";
94
+ case SIGABRT:
95
+ return "SIGABRT";
96
+ default:
97
+ return "signal";
98
+ }
99
+ }
100
+
101
+ void onFatalSignal(int signalNumber, siginfo_t *info, void * /*context*/) {
102
+ writeText("\n*** basalt: ");
103
+ writeText(nameFor(signalNumber));
104
+ if (info != nullptr) {
105
+ writeText(" at ");
106
+ writeHex(reinterpret_cast<std::uintptr_t>(info->si_addr));
107
+ }
108
+ // Said plainly, because the whole point is telling the loop apart from JS.
109
+ // `pthread_equal` on two stored values is a comparison and nothing more, so
110
+ // it is safe enough here even though it is not on the promised list.
111
+ if (gMainThreadKnown) {
112
+ writeText(::pthread_equal(::pthread_self(), gMainThread) != 0
113
+ ? ", the main thread"
114
+ : ", NOT the main thread");
115
+ }
116
+ writeText(", thread ");
117
+ // The id rather than the name: `pthread_getname_np` takes no lock on either
118
+ // platform but is not promised to be safe here, and the id is what the glog
119
+ // lines around this already print.
120
+ writeHex(static_cast<std::uintptr_t>(
121
+ reinterpret_cast<std::uintptr_t>(pthread_self())));
122
+ writeText(" ***\n");
123
+
124
+ // 64 frames: deep enough for a mounting walk inside a dispatch block inside a
125
+ // run loop, which is the stack this was written for.
126
+ void *frames[64];
127
+ const int count = ::backtrace(frames, 64);
128
+ // Writes straight to the descriptor and allocates nothing, unlike
129
+ // `backtrace_symbols`, which is why it is this one.
130
+ ::backtrace_symbols_fd(frames, count, STDERR_FILENO);
131
+ writeText("\n");
132
+
133
+ // Back to whatever was there, then re-raise. The exit status stays the signal,
134
+ // which is what the harness reports, and a handler the runtime installed for
135
+ // its own purposes still gets its turn.
136
+ const int slot = slotFor(signalNumber);
137
+ if (slot >= 0) {
138
+ ::sigaction(signalNumber, &gPrevious[slot], nullptr);
139
+ } else {
140
+ ::signal(signalNumber, SIG_DFL);
141
+ }
142
+ ::raise(signalNumber);
143
+ }
144
+
145
+ #else
146
+
147
+ LONG WINAPI onUnhandledException(EXCEPTION_POINTERS *info) {
148
+ // Not a signal handler, so this is allowed more than its POSIX counterpart.
149
+ // It stays just as narrow anyway: the process is already broken, and a handler
150
+ // that needs the heap to say so is a handler that says nothing when the heap
151
+ // is what broke.
152
+ char line[128];
153
+ const DWORD code =
154
+ info != nullptr && info->ExceptionRecord != nullptr
155
+ ? info->ExceptionRecord->ExceptionCode
156
+ : 0;
157
+ const void *at = info != nullptr && info->ExceptionRecord != nullptr
158
+ ? info->ExceptionRecord->ExceptionAddress
159
+ : nullptr;
160
+
161
+ int length = wsprintfA(line,
162
+ "\n*** basalt: exception 0x%08lx at %p, thread %lu ***\n",
163
+ static_cast<unsigned long>(code),
164
+ at,
165
+ GetCurrentThreadId());
166
+ DWORD written = 0;
167
+ WriteFile(GetStdHandle(STD_ERROR_HANDLE), line, static_cast<DWORD>(length), &written, nullptr);
168
+
169
+ // No symbols: a release build ships no .pdb, so dbghelp would resolve nothing
170
+ // and linking it would buy an address it already has. The module base is here
171
+ // so the frames below can be turned into offsets against the binary.
172
+ length = wsprintfA(line, "module %p\n", static_cast<void *>(GetModuleHandleW(nullptr)));
173
+ WriteFile(GetStdHandle(STD_ERROR_HANDLE), line, static_cast<DWORD>(length), &written, nullptr);
174
+
175
+ void *frames[64];
176
+ const USHORT count = CaptureStackBackTrace(0, 64, frames, nullptr);
177
+ for (USHORT i = 0; i < count; i++) {
178
+ length = wsprintfA(line, "%2u %p\n", static_cast<unsigned>(i), frames[i]);
179
+ WriteFile(GetStdHandle(STD_ERROR_HANDLE), line, static_cast<DWORD>(length), &written, nullptr);
180
+ }
181
+
182
+ // On to whatever Windows would have done, so the exit status and any crash
183
+ // reporting behave as before.
184
+ return EXCEPTION_CONTINUE_SEARCH;
185
+ }
186
+
187
+ #endif
188
+
189
+ } // namespace
190
+
191
+ namespace {
192
+
193
+ // BASALT_TEST_CRASH, which exists so that the handler can be tested at all.
194
+ //
195
+ // A handler is only ever exercised on the worst day, so one that was never run
196
+ // is a guess. This makes the worst day reproducible: the scenario in
197
+ // scripts/integration_test.py sets it and asserts the marker, a frame list and an
198
+ // exit status that is still the signal.
199
+ //
200
+ // `raise` rather than writing through a null pointer: a deliberate null
201
+ // dereference is undefined behaviour and a compiler is within its rights to
202
+ // delete it, which would make the test pass by not crashing.
203
+ void crashIfAsked() {
204
+ const char *value = std::getenv("BASALT_TEST_CRASH");
205
+ if (value == nullptr || *value == '\0') {
206
+ return;
207
+ }
208
+ #if defined(_WIN32)
209
+ // An access violation, since Windows has no raise(SIGSEGV) worth the name: the
210
+ // unhandled-exception filter is what is being tested, and only a real exception
211
+ // reaches it.
212
+ RaiseException(EXCEPTION_ACCESS_VIOLATION, 0, 0, nullptr);
213
+ #else
214
+ ::raise(SIGSEGV);
215
+ #endif
216
+ }
217
+
218
+ } // namespace
219
+
220
+ void installCrashHandler() {
221
+ #if defined(_WIN32)
222
+ SetUnhandledExceptionFilter(onUnhandledException);
223
+ crashIfAsked();
224
+ #else
225
+ // Once, while allocating is still allowed: glibc's first `backtrace` dlopens
226
+ // libgcc, and a handler is not the place to find that out.
227
+ void *warm[1];
228
+ (void)::backtrace(warm, 1);
229
+
230
+ // Called from main() on every host, so this is the main thread.
231
+ gMainThread = ::pthread_self();
232
+ gMainThreadKnown = true;
233
+
234
+ struct sigaction action = {};
235
+ action.sa_sigaction = onFatalSignal;
236
+ action.sa_flags = SA_SIGINFO | SA_ONSTACK;
237
+ sigemptyset(&action.sa_mask);
238
+
239
+ for (std::size_t i = 0; i < kFatalSignalCount; i++) {
240
+ ::sigaction(kFatalSignals[i], &action, &gPrevious[i]);
241
+ }
242
+ crashIfAsked();
243
+ #endif
244
+ }
245
+
246
+ } // namespace basalt
@@ -0,0 +1,65 @@
1
+ // What the host says when it dies.
2
+ //
3
+ // Written because it had nothing to say. On 2026-10-05 a reload teardown took
4
+ // SIGSEGV on CI's Mac, twice, and three attempts produced no address, no stack
5
+ // and no thread -- only the harness noticing the process was gone and reporting
6
+ // "host exited -11". The bug was found by reading `ReactHost` instead, and the
7
+ // next one will not be as guessable.
8
+ //
9
+ // Deliberately not glog's `InstallFailureSignalHandler`. That would be one line,
10
+ // and its signal list is fixed and includes SIGTERM -- which is how the harness
11
+ // ends a host that is working. A stack trace and a changed exit status there
12
+ // would break the scenarios that read both. See docs/ci-performance.md on
13
+ // BASALT_TEST_QUIT_FILE for what SIGTERM is load-bearing for.
14
+ //
15
+ // ## What it prints
16
+ //
17
+ // To stderr, which is where the harness is already looking:
18
+ //
19
+ // *** basalt: SIGSEGV at 0x0, thread 0x16dd57000 ***
20
+ // 0 basalt_appkit 0x0000000102a4c1b8 _ZN6basalt19AppKitMountingM...
21
+ // 1 basalt_appkit 0x0000000102a4b9e4 ...
22
+ //
23
+ // The names are mangled, and stay that way: demangling means
24
+ // `abi::__cxa_demangle`, which allocates, and a handler that needs the heap says
25
+ // nothing exactly when the heap is what broke. Pipe the log through `c++filt`.
26
+ //
27
+ // On Windows there are no symbols, for want of a .pdb in a release build, so it
28
+ // prints the module base and the return addresses: subtract the one from the
29
+ // others and the offsets resolve against the binary. Measured on CI, which is
30
+ // the only place this can be:
31
+ //
32
+ // *** basalt: exception 0xc0000005 at 00007FF92942EFDC, thread 2776 ***
33
+ // module 00007FF621B00000
34
+ // 0 00007FF621EE0094
35
+ // 1 00007FF9294A05BD
36
+ //
37
+ // Note the shape: `%p` there is upper case and carries no `0x`, unlike
38
+ // `backtrace_symbols_fd` above. Anything reading this output has to take both.
39
+ //
40
+ // ## What it may call
41
+ //
42
+ // Almost nothing. A signal handler may only use async-signal-safe functions, so
43
+ // there is no `LOG()`, no `std::string`, no formatting and no allocation here:
44
+ // glog takes a lock to write a line and a handler that waits for a lock held by
45
+ // the thread it interrupted never returns. `write`, `backtrace_symbols_fd`,
46
+ // `raise` and integer arithmetic are the whole vocabulary.
47
+ //
48
+ // `backtrace` is the exception: on glibc its first call dlopens libgcc, which
49
+ // allocates, so `installCrashHandler` calls it once while that is still safe.
50
+ //
51
+ // @format
52
+ #pragma once
53
+
54
+ namespace basalt {
55
+
56
+ // Installs handlers for the signals that mean the process is broken: SIGSEGV,
57
+ // SIGBUS, SIGILL, SIGFPE and SIGABRT. Not SIGTERM and not SIGINT, which mean
58
+ // somebody asked it to stop.
59
+ //
60
+ // Each handler prints, restores whatever was there before, and re-raises, so the
61
+ // exit status is still the signal and anything the runtime installed for its own
62
+ // purposes still runs. Call once, early, after logging is up.
63
+ void installCrashHandler();
64
+
65
+ } // namespace basalt
@@ -0,0 +1,110 @@
1
+ // What a React Native `cursor` value is called, in CSS's own words.
2
+ //
3
+ // The prop is a desktop prop: a hand over a button, an I-beam over text, a
4
+ // resize arrow over a divider. Every host ignored it, and
5
+ // backlog/desktop-capabilities.md said the opposite -- its entry on cursor
6
+ // control described itself as the work left "beyond what the `cursor` style
7
+ // property covers", which nothing covered.
8
+ //
9
+ // ## Why a name rather than a per-host enum
10
+ //
11
+ // React Native's `Cursor` is CSS's keyword list, and so is GDK's: GTK takes the
12
+ // keyword straight through `gtk_widget_set_cursor_from_name`. AppKit has its own
13
+ // small set of NSCursors and maps the names onto it, losing the ones macOS has no
14
+ // cursor for. Keeping the keyword as the thing that crosses the seam means there
15
+ // is one place that says what `cursor: 'grab'` is called and each view layer
16
+ // answers for its own platform, rather than two enums that can drift apart in
17
+ // their middles.
18
+ //
19
+ // Header-only and in core because both mounting managers need it and both know
20
+ // React Native; the view layers do not, and they take a string.
21
+
22
+ #pragma once
23
+
24
+ #include <react/renderer/components/view/primitives.h>
25
+
26
+ namespace basalt {
27
+
28
+ // The CSS keyword, or nullptr for "nothing to apply" -- which is `auto`, meaning
29
+ // the cursor is whatever the surrounding view says it is, and `url`, which in
30
+ // CSS names a custom image that React Native carries nothing for.
31
+ inline const char *cursorName(facebook::react::Cursor cursor) {
32
+ using facebook::react::Cursor;
33
+ switch (cursor) {
34
+ case Cursor::Auto:
35
+ return nullptr;
36
+ case Cursor::Alias:
37
+ return "alias";
38
+ case Cursor::AllScroll:
39
+ return "all-scroll";
40
+ case Cursor::Cell:
41
+ return "cell";
42
+ case Cursor::ColResize:
43
+ return "col-resize";
44
+ case Cursor::ContextMenu:
45
+ return "context-menu";
46
+ case Cursor::Copy:
47
+ return "copy";
48
+ case Cursor::Crosshair:
49
+ return "crosshair";
50
+ case Cursor::Default:
51
+ return "default";
52
+ case Cursor::EResize:
53
+ return "e-resize";
54
+ case Cursor::EWResize:
55
+ return "ew-resize";
56
+ case Cursor::Grab:
57
+ return "grab";
58
+ case Cursor::Grabbing:
59
+ return "grabbing";
60
+ case Cursor::Help:
61
+ return "help";
62
+ case Cursor::Move:
63
+ return "move";
64
+ case Cursor::NEResize:
65
+ return "ne-resize";
66
+ case Cursor::NESWResize:
67
+ return "nesw-resize";
68
+ case Cursor::NResize:
69
+ return "n-resize";
70
+ case Cursor::NSResize:
71
+ return "ns-resize";
72
+ case Cursor::NWResize:
73
+ return "nw-resize";
74
+ case Cursor::NWSEResize:
75
+ return "nwse-resize";
76
+ case Cursor::NoDrop:
77
+ return "no-drop";
78
+ case Cursor::None:
79
+ return "none";
80
+ case Cursor::NotAllowed:
81
+ return "not-allowed";
82
+ case Cursor::Pointer:
83
+ return "pointer";
84
+ case Cursor::Progress:
85
+ return "progress";
86
+ case Cursor::RowResize:
87
+ return "row-resize";
88
+ case Cursor::SResize:
89
+ return "s-resize";
90
+ case Cursor::SEResize:
91
+ return "se-resize";
92
+ case Cursor::SWResize:
93
+ return "sw-resize";
94
+ case Cursor::Text:
95
+ return "text";
96
+ case Cursor::Url:
97
+ return nullptr;
98
+ case Cursor::WResize:
99
+ return "w-resize";
100
+ case Cursor::Wait:
101
+ return "wait";
102
+ case Cursor::ZoomIn:
103
+ return "zoom-in";
104
+ case Cursor::ZoomOut:
105
+ return "zoom-out";
106
+ }
107
+ return nullptr;
108
+ }
109
+
110
+ } // namespace basalt
@@ -73,11 +73,12 @@ void showDevMenu(facebook::react::ReactHost *reactHost, double x, double y) {
73
73
  request.y = y;
74
74
  request.entries.reserve(items->size());
75
75
  for (const DevMenuItem &item : *items) {
76
- request.entries.push_back(MenuEntry{
77
- .label = item.label,
78
- .enabled = true,
79
- .shortcut = item.shortcut,
80
- });
76
+ // Assigned, not a designated initializer: see MenuEntry::separator for why
77
+ // naming some fields and not others does not survive a new one.
78
+ MenuEntry entry;
79
+ entry.label = item.label;
80
+ entry.shortcut = item.shortcut;
81
+ request.entries.push_back(std::move(entry));
81
82
  }
82
83
 
83
84
  // Through presentMenu rather than showMenu, so BASALT_TEST_MENU can answer
@@ -0,0 +1,87 @@
1
+ #include "DeviceMetrics.h"
2
+
3
+ #include "FontScaling.h"
4
+
5
+ namespace basalt {
6
+
7
+ namespace {
8
+
9
+ // Which display a point is on, or nothing when no display contains it -- a
10
+ // window dragged to a monitor that has just been unplugged, or a host that has
11
+ // reported no displays at all.
12
+ const DisplayInfo *displayContaining(const std::vector<DisplayInfo> &displays,
13
+ double x,
14
+ double y) {
15
+ for (const DisplayInfo &display : displays) {
16
+ if (x >= display.x && x < display.x + display.width && y >= display.y &&
17
+ y < display.y + display.height) {
18
+ return &display;
19
+ }
20
+ }
21
+ return nullptr;
22
+ }
23
+
24
+ const DisplayInfo *primaryDisplay(const std::vector<DisplayInfo> &displays) {
25
+ for (const DisplayInfo &display : displays) {
26
+ if (display.primary) {
27
+ return &display;
28
+ }
29
+ }
30
+ return displays.empty() ? nullptr : &displays.front();
31
+ }
32
+
33
+ } // namespace
34
+
35
+ DeviceMetrics deviceMetricsFrom(const WindowBounds &window,
36
+ double surfaceWidth,
37
+ double surfaceHeight,
38
+ const std::vector<DisplayInfo> &displays,
39
+ double fontScale) {
40
+ const double scale = fontScale > 0.0 ? fontScale : 1.0;
41
+
42
+ // The display the window's centre is on, falling back to the primary one: a
43
+ // window that has not been placed yet is at the origin, which is the primary
44
+ // display's corner on every host.
45
+ const DisplayInfo *on = displayContaining(displays,
46
+ window.x + window.width / 2.0,
47
+ window.y + window.height / 2.0);
48
+ if (on == nullptr) {
49
+ on = primaryDisplay(displays);
50
+ }
51
+
52
+ DeviceMetrics metrics;
53
+ metrics.window.scale = on != nullptr && on->scaleFactor > 0.0 ? on->scaleFactor : 1.0;
54
+ metrics.window.fontScale = scale;
55
+ // The surface when the host has started one, which is the area an app can
56
+ // lay out in; the window's frame otherwise.
57
+ metrics.window.width = surfaceWidth > 0.0 ? surfaceWidth : window.width;
58
+ metrics.window.height = surfaceHeight > 0.0 ? surfaceHeight : window.height;
59
+
60
+ metrics.screen = metrics.window;
61
+ if (on != nullptr) {
62
+ metrics.screen.width = on->width;
63
+ metrics.screen.height = on->height;
64
+ }
65
+
66
+ // Nothing reported yet. The screen is a better answer than zero for an app
67
+ // laying out during its first render; see the header.
68
+ if (metrics.window.width <= 0.0 || metrics.window.height <= 0.0) {
69
+ metrics.window.width = metrics.screen.width;
70
+ metrics.window.height = metrics.screen.height;
71
+ }
72
+
73
+ return metrics;
74
+ }
75
+
76
+ DeviceMetrics currentDeviceMetrics() {
77
+ double surfaceWidth = 0.0;
78
+ double surfaceHeight = 0.0;
79
+ lastKnownSurfaceSize(&surfaceWidth, &surfaceHeight);
80
+ return deviceMetricsFrom(lastKnownWindowBounds(),
81
+ surfaceWidth,
82
+ surfaceHeight,
83
+ lastKnownDisplays(),
84
+ static_cast<double>(systemFontScale()));
85
+ }
86
+
87
+ } // namespace basalt
@@ -0,0 +1,80 @@
1
+ // What `Dimensions` and `PixelRatio` answer with.
2
+ //
3
+ // React Native's `Dimensions.get('window')` is one of the most-used APIs it
4
+ // has, and on this platform it reported a fiction: ReactCxxPlatform's
5
+ // `DeviceInfoModule::getConstants` returns a hardcoded 1280x720 with a
6
+ // `// TODO: Wire this to come from the actual app size` beside it and no seam
7
+ // to wire. `scale` came back 0 and `fontScale` was never initialised at all --
8
+ // the generated struct leaves it so -- which made `PixelRatio.get()` zero and
9
+ // `PixelRatio.roundToNearestPixel(x)` NaN, silently, in every app.
10
+ //
11
+ // Everything needed is already here: `lastKnownWindowBounds()` is the window
12
+ // the host reports on every change, `lastKnownDisplays()` carries each
13
+ // display's scale factor, and `systemFontScale()` is the text scale the font
14
+ // machinery already multiplies by. This is the arithmetic that turns the three
15
+ // into React Native's two payloads, written once so three hosts cannot
16
+ // disagree about which display a window is on.
17
+
18
+ #pragma once
19
+
20
+ #include "WindowControl.h"
21
+
22
+ #include <vector>
23
+
24
+ namespace basalt {
25
+
26
+ // One of React Native's `DisplayMetrics`: a size in logical pixels, how many
27
+ // physical pixels each of those is, and the text scale.
28
+ struct DisplayMetrics {
29
+ double width{0.0};
30
+ double height{0.0};
31
+ // Never zero. A caller dividing by it -- which is exactly what
32
+ // `PixelRatio.roundToNearestPixel` does -- should not have to check.
33
+ double scale{1.0};
34
+ double fontScale{1.0};
35
+ };
36
+
37
+ struct DeviceMetrics {
38
+ DisplayMetrics window;
39
+ DisplayMetrics screen;
40
+
41
+ bool operator==(const DeviceMetrics &other) const {
42
+ return window.width == other.window.width && window.height == other.window.height &&
43
+ window.scale == other.window.scale && window.fontScale == other.window.fontScale &&
44
+ screen.width == other.screen.width && screen.height == other.screen.height &&
45
+ screen.scale == other.screen.scale && screen.fontScale == other.screen.fontScale;
46
+ }
47
+ bool operator!=(const DeviceMetrics &other) const { return !(*this == other); }
48
+ };
49
+
50
+ // The window and the screen it is on, from what the host has reported.
51
+ //
52
+ // `window` is the window's own size; `screen` is the display it sits on, which
53
+ // is the distinction React Native draws and which matters on a desktop in a way
54
+ // it never does on a phone: a window is a fraction of a screen, and an app
55
+ // deciding how much it can show wants the first while one deciding where to put
56
+ // itself wants the second.
57
+ //
58
+ // Which display a window is on is decided by its centre, so a window straddling
59
+ // two reports the one most of it is on. A window the host has not reported yet
60
+ // -- everything is zero -- borrows the primary display's size rather than
61
+ // reporting a zero an app would divide by; that is the first render, before the
62
+ // first layout, and a zero there is worse than an estimate.
63
+ // `surfaceWidth` and `surfaceHeight` are the area React lays out in, which is
64
+ // what `window` reports when it is known: a desktop window's frame includes a
65
+ // title bar the app cannot put anything in, and the two differ by exactly that
66
+ // on AppKit. Zero means the host has not started a surface yet, and then the
67
+ // window's own size is used instead.
68
+ DeviceMetrics deviceMetricsFrom(const WindowBounds &window,
69
+ double surfaceWidth,
70
+ double surfaceHeight,
71
+ const std::vector<DisplayInfo> &displays,
72
+ double fontScale);
73
+
74
+ // The same, from what the host last reported and what the font machinery is
75
+ // scaling by. Safe from any thread, which is the point: a TurboModule method
76
+ // runs on the JavaScript thread and the toolkit may only be asked on the UI
77
+ // one.
78
+ DeviceMetrics currentDeviceMetrics();
79
+
80
+ } // namespace basalt