basalt-win32 0.0.0-stage → 0.2.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.
- package/LICENSE +21 -0
- package/cli/runWindows.js +35 -0
- package/cli/shared.js +75 -0
- package/native/CMakeLists.txt +429 -0
- package/native/tests/test_win32_accessibility.cpp +585 -0
- package/native/tests/test_win32_animated_image.cpp +351 -0
- package/native/tests/test_win32_clipboard.cpp +121 -0
- package/native/tests/test_win32_focus.cpp +195 -0
- package/native/tests/test_win32_hittest.cpp +750 -0
- package/native/tests/test_win32_image.cpp +370 -0
- package/native/tests/test_win32_imageloader.cpp +208 -0
- package/native/tests/test_win32_input.cpp +511 -0
- package/native/tests/test_win32_key_events.cpp +84 -0
- package/native/tests/test_win32_mounting.cpp +757 -0
- package/native/tests/test_win32_paint.cpp +1799 -0
- package/native/tests/test_win32_scrollview.cpp +647 -0
- package/native/tests/test_win32_selection.cpp +215 -0
- package/native/tests/test_win32_text.cpp +1456 -0
- package/native/tests/test_win32_textinput.cpp +1594 -0
- package/native/tests/test_win32_titlebar.cpp +111 -0
- package/native/tests/test_win32_titlebar_actions.cpp +62 -0
- package/native/tests/test_win32_view.cpp +222 -0
- package/native/win32/ColorSchemeWin32.cpp +56 -0
- package/native/win32/ComponentRegistryWin32.cpp +156 -0
- package/native/win32/DirectWriteLayout.cpp +485 -0
- package/native/win32/DirectWriteLayout.h +77 -0
- package/native/win32/DirectWriteLayoutManager.cpp +154 -0
- package/native/win32/FontRegistryDirectWrite.cpp +117 -0
- package/native/win32/PlatformServicesWin32.cpp +744 -0
- package/native/win32/RnWin32Accessible.cpp +343 -0
- package/native/win32/RnWin32Accessible.h +130 -0
- package/native/win32/RnWin32Image.cpp +588 -0
- package/native/win32/RnWin32Image.h +139 -0
- package/native/win32/RnWin32TextLayout.cpp +1379 -0
- package/native/win32/RnWin32TextLayout.h +390 -0
- package/native/win32/RnWin32View.cpp +2956 -0
- package/native/win32/RnWin32View.h +926 -0
- package/native/win32/Win32AnimationChoreographer.cpp +76 -0
- package/native/win32/Win32AnimationChoreographer.h +61 -0
- package/native/win32/Win32Clip.h +209 -0
- package/native/win32/Win32Cursors.cpp +309 -0
- package/native/win32/Win32Cursors.h +121 -0
- package/native/win32/Win32DropTarget.cpp +567 -0
- package/native/win32/Win32DropTarget.h +42 -0
- package/native/win32/Win32Focus.cpp +262 -0
- package/native/win32/Win32Focus.h +109 -0
- package/native/win32/Win32ImageLoader.cpp +199 -0
- package/native/win32/Win32ImageLoader.h +133 -0
- package/native/win32/Win32InputScopes.h +92 -0
- package/native/win32/Win32KeyEvents.cpp +126 -0
- package/native/win32/Win32KeyEvents.h +20 -0
- package/native/win32/Win32MenuBar.cpp +382 -0
- package/native/win32/Win32MenuBar.h +16 -0
- package/native/win32/Win32MountingManager.cpp +1282 -0
- package/native/win32/Win32MountingManager.h +243 -0
- package/native/win32/Win32Offscreen.h +53 -0
- package/native/win32/Win32Packaging.cpp +161 -0
- package/native/win32/Win32Packaging.h +56 -0
- package/native/win32/Win32RunLoopObserver.cpp +48 -0
- package/native/win32/Win32RunLoopObserver.h +42 -0
- package/native/win32/Win32ScrollView.cpp +718 -0
- package/native/win32/Win32ScrollView.h +256 -0
- package/native/win32/Win32SkiaContext.cpp +221 -0
- package/native/win32/Win32SkiaContext.h +53 -0
- package/native/win32/Win32SkiaModule.cpp +80 -0
- package/native/win32/Win32SkiaModule.h +50 -0
- package/native/win32/Win32SkiaPeer.cpp +358 -0
- package/native/win32/Win32SkiaPeer.h +66 -0
- package/native/win32/Win32SkiaUnicode.cpp +42 -0
- package/native/win32/Win32Snapshot.cpp +201 -0
- package/native/win32/Win32Snapshot.h +66 -0
- package/native/win32/Win32Strings.h +54 -0
- package/native/win32/Win32TextInput.cpp +1296 -0
- package/native/win32/Win32TextInput.h +341 -0
- package/native/win32/Win32TitleBar.cpp +562 -0
- package/native/win32/Win32TitleBar.h +146 -0
- package/native/win32/Win32TitleBarLayout.cpp +46 -0
- package/native/win32/Win32TitleBarLayout.h +58 -0
- package/native/win32/Win32TouchDispatcher.cpp +577 -0
- package/native/win32/Win32TouchDispatcher.h +201 -0
- package/native/win32/Win32UiThread.h +27 -0
- package/native/win32/Win32WindowControl.cpp +329 -0
- package/native/win32/Win32WindowModule.cpp +209 -0
- package/native/win32/Win32WindowModule.h +72 -0
- package/native/win32/demo_layout_win32.cpp +232 -0
- package/native/win32/main_win32.cpp +2722 -0
- package/native/win32/mount_harness_win32.cpp +316 -0
- package/package.json +25 -4
- package/react-native.config.js +22 -0
- package/README.md +0 -3
|
@@ -0,0 +1,750 @@
|
|
|
1
|
+
// Hit testing.
|
|
2
|
+
//
|
|
3
|
+
// The same questions tests/test_hittest.cpp asks of GTK and the hit-test half
|
|
4
|
+
// of tests/test_appkit_input.mm asks of AppKit, in the same order, plus two
|
|
5
|
+
// this platform has to answer for itself.
|
|
6
|
+
//
|
|
7
|
+
// Both other hosts inherit picking from their toolkit -- `gtk_widget_pick` and
|
|
8
|
+
// `-[NSView hitTest:]` -- so the work there is making the toolkit's geometry
|
|
9
|
+
// agree with Fabric's. There is no toolkit geometry here, so this is written
|
|
10
|
+
// out, which is more code and one fewer thing that can silently disagree: the
|
|
11
|
+
// transform a view is drawn with is the transform a press is tested against,
|
|
12
|
+
// because `localToParent` is the only place either of them is composed.
|
|
13
|
+
|
|
14
|
+
#include "TestHarness.h"
|
|
15
|
+
|
|
16
|
+
#include "RnWin32View.h"
|
|
17
|
+
#include "Win32Cursors.h"
|
|
18
|
+
#include "Win32InputScopes.h"
|
|
19
|
+
|
|
20
|
+
#include <memory>
|
|
21
|
+
#include <sstream>
|
|
22
|
+
#include <string>
|
|
23
|
+
#include <vector>
|
|
24
|
+
|
|
25
|
+
using basalt::win32::hitTest;
|
|
26
|
+
using basalt::win32::RnWin32View;
|
|
27
|
+
|
|
28
|
+
namespace {
|
|
29
|
+
|
|
30
|
+
class Tree {
|
|
31
|
+
public:
|
|
32
|
+
RnWin32View *box(int32_t tag, float x, float y, float w, float h) {
|
|
33
|
+
auto view = std::make_unique<RnWin32View>(tag);
|
|
34
|
+
view->setFrame(x, y, w, h);
|
|
35
|
+
RnWin32View *raw = view.get();
|
|
36
|
+
views_.push_back(std::move(view));
|
|
37
|
+
return raw;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private:
|
|
41
|
+
std::vector<std::unique_ptr<RnWin32View>> views_;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// The tag of whatever is under a point, or -1 for a miss. Comparing tags rather
|
|
45
|
+
// than pointers makes a failure say which view it found.
|
|
46
|
+
int32_t tagAt(RnWin32View *root, float x, float y) {
|
|
47
|
+
RnWin32View *hit = hitTest(root, x, y);
|
|
48
|
+
return hit == nullptr ? -1 : hit->tag();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
} // namespace
|
|
52
|
+
|
|
53
|
+
TEST(hit_test_finds_the_view_under_a_point) {
|
|
54
|
+
Tree tree;
|
|
55
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
56
|
+
root->insertChild(tree.box(2, 50, 50, 100, 100), 0);
|
|
57
|
+
|
|
58
|
+
EXPECT_EQ(tagAt(root, 100, 100), 2);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
TEST(hit_test_uses_top_left_coordinates) {
|
|
62
|
+
Tree tree;
|
|
63
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
64
|
+
// Near the top of the root. On a coordinate system with the origin at the
|
|
65
|
+
// bottom -- AppKit's, unflipped -- this point would be over nothing.
|
|
66
|
+
root->insertChild(tree.box(2, 0, 0, 400, 40), 0);
|
|
67
|
+
|
|
68
|
+
EXPECT_EQ(tagAt(root, 200, 20), 2);
|
|
69
|
+
EXPECT_EQ(tagAt(root, 200, 380), 1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
TEST(hit_test_misses_return_the_root_not_a_child) {
|
|
73
|
+
Tree tree;
|
|
74
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
75
|
+
root->insertChild(tree.box(2, 50, 50, 100, 100), 0);
|
|
76
|
+
|
|
77
|
+
EXPECT_EQ(tagAt(root, 300, 300), 1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
TEST(hit_test_outside_the_root_is_a_miss) {
|
|
81
|
+
Tree tree;
|
|
82
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
83
|
+
|
|
84
|
+
EXPECT_EQ(tagAt(root, 500, 200), -1);
|
|
85
|
+
EXPECT_EQ(tagAt(root, -1, 200), -1);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
TEST(hit_test_returns_the_deepest_view) {
|
|
89
|
+
Tree tree;
|
|
90
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
91
|
+
RnWin32View *middle = tree.box(2, 50, 50, 200, 200);
|
|
92
|
+
middle->insertChild(tree.box(3, 25, 25, 50, 50), 0);
|
|
93
|
+
root->insertChild(middle, 0);
|
|
94
|
+
|
|
95
|
+
// (100,100) is inside all three; the innermost wins.
|
|
96
|
+
EXPECT_EQ(tagAt(root, 100, 100), 3);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
TEST(hit_test_respects_sibling_order) {
|
|
100
|
+
Tree tree;
|
|
101
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
102
|
+
root->insertChild(tree.box(2, 0, 0, 200, 200), 0);
|
|
103
|
+
root->insertChild(tree.box(3, 0, 0, 200, 200), 1);
|
|
104
|
+
|
|
105
|
+
// Exactly overlapping. The later child is painted on top, so it is the one a
|
|
106
|
+
// press lands on.
|
|
107
|
+
EXPECT_EQ(tagAt(root, 100, 100), 3);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
TEST(hit_test_follows_z_index) {
|
|
111
|
+
Tree tree;
|
|
112
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
113
|
+
RnWin32View *under = tree.box(2, 0, 0, 200, 200);
|
|
114
|
+
RnWin32View *over = tree.box(3, 0, 0, 200, 200);
|
|
115
|
+
root->insertChild(under, 0);
|
|
116
|
+
root->insertChild(over, 1);
|
|
117
|
+
|
|
118
|
+
// A negative zIndex on the later child puts it underneath, and a press has to
|
|
119
|
+
// follow the paint order rather than the child list -- otherwise a view can
|
|
120
|
+
// be drawn behind another and still swallow its clicks.
|
|
121
|
+
over->setZIndex(-1);
|
|
122
|
+
EXPECT_EQ(tagAt(root, 100, 100), 2);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
TEST(hit_test_follows_a_scroll_offset) {
|
|
126
|
+
Tree tree;
|
|
127
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
128
|
+
RnWin32View *scroller = tree.box(50, 0, 0, 200, 200);
|
|
129
|
+
scroller->insertChild(tree.box(51, 0, 300, 200, 100), 0);
|
|
130
|
+
root->insertChild(scroller, 0);
|
|
131
|
+
|
|
132
|
+
// The row sits below the viewport, so nothing of it is under this point.
|
|
133
|
+
EXPECT_EQ(tagAt(root, 100, 50), 50);
|
|
134
|
+
|
|
135
|
+
scroller->setScrollOffset(0, 300);
|
|
136
|
+
EXPECT_EQ(tagAt(root, 100, 50), 51);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
TEST(hit_test_skips_hidden_views) {
|
|
140
|
+
Tree tree;
|
|
141
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
142
|
+
RnWin32View *hidden = tree.box(2, 0, 0, 200, 200);
|
|
143
|
+
root->insertChild(hidden, 0);
|
|
144
|
+
|
|
145
|
+
EXPECT_EQ(tagAt(root, 100, 100), 2);
|
|
146
|
+
|
|
147
|
+
// `display: none` takes a view out of painting and out of picking together.
|
|
148
|
+
// Distinct from opacity 0, which is still there to be pressed.
|
|
149
|
+
hidden->setHidden(true);
|
|
150
|
+
EXPECT_EQ(tagAt(root, 100, 100), 1);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
TEST(hit_test_skips_the_children_of_a_hidden_view) {
|
|
154
|
+
Tree tree;
|
|
155
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
156
|
+
RnWin32View *hidden = tree.box(2, 0, 0, 200, 200);
|
|
157
|
+
hidden->insertChild(tree.box(3, 0, 0, 100, 100), 0);
|
|
158
|
+
root->insertChild(hidden, 0);
|
|
159
|
+
|
|
160
|
+
hidden->setHidden(true);
|
|
161
|
+
EXPECT_EQ(tagAt(root, 50, 50), 1);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// `backfaceVisibility: 'hidden'` takes the view out of picking as well as out
|
|
165
|
+
// of the picture -- the back of a card is not clickable.
|
|
166
|
+
TEST(hit_test_skips_a_view_turned_away) {
|
|
167
|
+
Tree tree;
|
|
168
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
169
|
+
RnWin32View *card = tree.box(2, 0, 0, 200, 200);
|
|
170
|
+
card->setHidesBackFace(true);
|
|
171
|
+
root->insertChild(card, 0);
|
|
172
|
+
|
|
173
|
+
EXPECT_EQ(tagAt(root, 100, 100), 2);
|
|
174
|
+
|
|
175
|
+
// Half a turn about Y, which mirrors it.
|
|
176
|
+
const float flipped[16] = {-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1};
|
|
177
|
+
card->setTransform(flipped);
|
|
178
|
+
EXPECT_EQ(tagAt(root, 100, 100), 1);
|
|
179
|
+
|
|
180
|
+
// Turning the prop off shows it again. The early return this used to have
|
|
181
|
+
// left the view hidden for good.
|
|
182
|
+
card->setHidesBackFace(false);
|
|
183
|
+
EXPECT_EQ(tagAt(root, 100, 100), 2);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// The two reasons a view can be hidden are independent, and Fabric applies
|
|
187
|
+
// them through different calls: props first, then layout metrics. So a card
|
|
188
|
+
// turned away from the viewer had `display: none`'s "no" written over the top
|
|
189
|
+
// of it on the very same mount, and came back.
|
|
190
|
+
TEST(display_none_and_a_back_face_do_not_cancel_each_other) {
|
|
191
|
+
Tree tree;
|
|
192
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
193
|
+
RnWin32View *card = tree.box(2, 0, 0, 200, 200);
|
|
194
|
+
card->setHidesBackFace(true);
|
|
195
|
+
root->insertChild(card, 0);
|
|
196
|
+
|
|
197
|
+
const float flipped[16] = {-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1};
|
|
198
|
+
card->setTransform(flipped);
|
|
199
|
+
EXPECT_EQ(tagAt(root, 100, 100), 1);
|
|
200
|
+
|
|
201
|
+
// What Win32MountingManager says about every view that is not display: none.
|
|
202
|
+
card->setHidden(false);
|
|
203
|
+
EXPECT_EQ(tagAt(root, 100, 100), 1);
|
|
204
|
+
|
|
205
|
+
// And the other way: facing the viewer must not reveal what the app hid.
|
|
206
|
+
card->setHidden(true);
|
|
207
|
+
card->setTransform(nullptr);
|
|
208
|
+
EXPECT_EQ(tagAt(root, 100, 100), 1);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
TEST(hit_test_follows_a_transform) {
|
|
212
|
+
Tree tree;
|
|
213
|
+
RnWin32View *root = tree.box(1, 0, 0, 100, 100);
|
|
214
|
+
// The same wide, short card the paint tests rotate, at the same coordinates,
|
|
215
|
+
// so the two suites corroborate rather than each asserting its own idea of
|
|
216
|
+
// where the thing went.
|
|
217
|
+
RnWin32View *card = tree.box(2, 10, 40, 80, 20);
|
|
218
|
+
root->insertChild(card, 0);
|
|
219
|
+
|
|
220
|
+
EXPECT_EQ(tagAt(root, 20, 50), 2);
|
|
221
|
+
EXPECT_EQ(tagAt(root, 50, 20), 1);
|
|
222
|
+
|
|
223
|
+
// A quarter turn about the centre: the card now covers x 40..60, y 10..90.
|
|
224
|
+
const float quarterTurn[16] = {
|
|
225
|
+
0, 1, 0, 0, //
|
|
226
|
+
-1, 0, 0, 0, //
|
|
227
|
+
0, 0, 1, 0, //
|
|
228
|
+
0, 0, 0, 1};
|
|
229
|
+
card->setTransform(quarterTurn);
|
|
230
|
+
|
|
231
|
+
// This is the assertion the whole inverse-matrix path exists for. A platform
|
|
232
|
+
// that draws the transform but does not pick through it leaves a rotated
|
|
233
|
+
// button clickable where it used to be, and both of these still pass the
|
|
234
|
+
// *first* way round -- which is why both are here.
|
|
235
|
+
EXPECT_EQ(tagAt(root, 50, 20), 2);
|
|
236
|
+
EXPECT_EQ(tagAt(root, 20, 50), 1);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
TEST(hit_test_on_a_null_root_is_a_miss) {
|
|
240
|
+
EXPECT_EQ(tagAt(nullptr, 10, 10), -1);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// `pointerEvents`, which decides what a press can land on rather than what is
|
|
244
|
+
// drawn. The same four cases the GTK and AppKit suites ask, in the same order,
|
|
245
|
+
// because the answer has to be the same on all three.
|
|
246
|
+
TEST(pointer_events_none_passes_a_press_through_to_what_is_behind) {
|
|
247
|
+
Tree tree;
|
|
248
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
249
|
+
RnWin32View *behind = tree.box(10, 0, 0, 200, 200);
|
|
250
|
+
RnWin32View *over = tree.box(11, 0, 0, 200, 200);
|
|
251
|
+
root->insertChild(behind, 0);
|
|
252
|
+
root->insertChild(over, 1);
|
|
253
|
+
|
|
254
|
+
// Painted last, so it is on top and takes the press.
|
|
255
|
+
EXPECT_EQ(tagAt(root, 50, 50), 11);
|
|
256
|
+
|
|
257
|
+
over->setPointerEvents(RnWin32View::PointerEvents::None);
|
|
258
|
+
EXPECT_EQ(tagAt(root, 50, 50), 10);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
TEST(pointer_events_none_takes_the_children_with_it) {
|
|
262
|
+
Tree tree;
|
|
263
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
264
|
+
RnWin32View *over = tree.box(11, 0, 0, 200, 200);
|
|
265
|
+
over->insertChild(tree.box(12, 0, 0, 100, 100), 0);
|
|
266
|
+
root->insertChild(over, 0);
|
|
267
|
+
|
|
268
|
+
EXPECT_EQ(tagAt(root, 50, 50), 12);
|
|
269
|
+
// The whole subtree leaves hit testing, not just the view the prop is on.
|
|
270
|
+
over->setPointerEvents(RnWin32View::PointerEvents::None);
|
|
271
|
+
EXPECT_EQ(tagAt(root, 50, 50), 1);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
TEST(pointer_events_box_none_is_transparent_and_its_children_are_not) {
|
|
275
|
+
Tree tree;
|
|
276
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
277
|
+
RnWin32View *behind = tree.box(10, 0, 0, 300, 300);
|
|
278
|
+
RnWin32View *overlay = tree.box(11, 0, 0, 300, 300);
|
|
279
|
+
overlay->insertChild(tree.box(12, 0, 0, 100, 100), 0);
|
|
280
|
+
root->insertChild(behind, 0);
|
|
281
|
+
root->insertChild(overlay, 1);
|
|
282
|
+
overlay->setPointerEvents(RnWin32View::PointerEvents::BoxNone);
|
|
283
|
+
|
|
284
|
+
// Over the child: the overlay's children are still targets.
|
|
285
|
+
EXPECT_EQ(tagAt(root, 50, 50), 12);
|
|
286
|
+
// Over the overlay and nothing inside it: the press belongs to the view
|
|
287
|
+
// *behind*, not to the overlay's parent. That is the whole reason the mode
|
|
288
|
+
// exists, and returning the parent would look right until something was
|
|
289
|
+
// underneath.
|
|
290
|
+
EXPECT_EQ(tagAt(root, 200, 200), 10);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
TEST(pointer_events_box_only_swallows_presses_meant_for_its_children) {
|
|
294
|
+
Tree tree;
|
|
295
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
296
|
+
RnWin32View *panel = tree.box(11, 0, 0, 300, 300);
|
|
297
|
+
panel->insertChild(tree.box(12, 0, 0, 100, 100), 0);
|
|
298
|
+
root->insertChild(panel, 0);
|
|
299
|
+
|
|
300
|
+
EXPECT_EQ(tagAt(root, 50, 50), 12);
|
|
301
|
+
panel->setPointerEvents(RnWin32View::PointerEvents::BoxOnly);
|
|
302
|
+
// The press lands on the panel even though it is over the child, which is
|
|
303
|
+
// what makes a disabled panel disable everything in it.
|
|
304
|
+
EXPECT_EQ(tagAt(root, 50, 50), 11);
|
|
305
|
+
EXPECT_EQ(tagAt(root, 200, 200), 11);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
TEST(pointer_events_shows_up_in_the_tree_dump) {
|
|
309
|
+
Tree tree;
|
|
310
|
+
RnWin32View *view = tree.box(10, 0, 0, 100, 100);
|
|
311
|
+
view->setPointerEvents(RnWin32View::PointerEvents::BoxNone);
|
|
312
|
+
// Printed because it is invisible otherwise: a view with this prop is drawn
|
|
313
|
+
// exactly like one without, so the cross-host diff can only see the prop
|
|
314
|
+
// arrived if each host prints it.
|
|
315
|
+
EXPECT(view->describeTree().find("pe=box-none") != std::string::npos);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// --------------------------------------------------------------------------
|
|
319
|
+
// offsetPoint
|
|
320
|
+
// --------------------------------------------------------------------------
|
|
321
|
+
//
|
|
322
|
+
// `pageToLocal` is what `Touch::offsetPoint` is built from, and it inverts the
|
|
323
|
+
// same chain `hitTest` above inverts on the way down. The AppKit suite has the
|
|
324
|
+
// same five cases against `rnPageToLocal:fromRoot:into:`.
|
|
325
|
+
|
|
326
|
+
TEST(page_to_local_subtracts_the_ancestors) {
|
|
327
|
+
Tree tree;
|
|
328
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
329
|
+
RnWin32View *middle = tree.box(10, 50, 60, 300, 300);
|
|
330
|
+
RnWin32View *leaf = tree.box(20, 20, 30, 100, 100);
|
|
331
|
+
root->insertChild(middle, 0);
|
|
332
|
+
middle->insertChild(leaf, 0);
|
|
333
|
+
|
|
334
|
+
float x = 0.0f;
|
|
335
|
+
float y = 0.0f;
|
|
336
|
+
EXPECT(leaf->pageToLocal(root, 100.0f, 120.0f, x, y));
|
|
337
|
+
// 100 - 50 - 20, and 120 - 60 - 30.
|
|
338
|
+
EXPECT_NEAR(x, 30.0f, 0.001f);
|
|
339
|
+
EXPECT_NEAR(y, 30.0f, 0.001f);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// The root's own point is the page point, which is the case that made the old
|
|
343
|
+
// behaviour look right for as long as it did.
|
|
344
|
+
TEST(page_to_local_at_the_root_is_the_page_point) {
|
|
345
|
+
Tree tree;
|
|
346
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
347
|
+
|
|
348
|
+
float x = 0.0f;
|
|
349
|
+
float y = 0.0f;
|
|
350
|
+
EXPECT(root->pageToLocal(root, 100.0f, 120.0f, x, y));
|
|
351
|
+
EXPECT_NEAR(x, 100.0f, 0.001f);
|
|
352
|
+
EXPECT_NEAR(y, 120.0f, 0.001f);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
TEST(page_to_local_follows_a_transformed_ancestor) {
|
|
356
|
+
Tree tree;
|
|
357
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
358
|
+
RnWin32View *moved = tree.box(10, 0, 0, 100, 100);
|
|
359
|
+
// Shifted 100 right and 50 down by a transform rather than by its frame.
|
|
360
|
+
const float translate[16] = {
|
|
361
|
+
1, 0, 0, 0, //
|
|
362
|
+
0, 1, 0, 0, //
|
|
363
|
+
0, 0, 1, 0, //
|
|
364
|
+
100, 50, 0, 1};
|
|
365
|
+
moved->setTransform(translate);
|
|
366
|
+
root->insertChild(moved, 0);
|
|
367
|
+
|
|
368
|
+
float x = 0.0f;
|
|
369
|
+
float y = 0.0f;
|
|
370
|
+
EXPECT(moved->pageToLocal(root, 110.0f, 60.0f, x, y));
|
|
371
|
+
// The press is 10 into the view as drawn, not 110.
|
|
372
|
+
EXPECT_NEAR(x, 10.0f, 0.001f);
|
|
373
|
+
EXPECT_NEAR(y, 10.0f, 0.001f);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// A scrolled ancestor moves its children, and the offset has to come back out
|
|
377
|
+
// -- the same offset `hitTest` adds on the way down.
|
|
378
|
+
TEST(page_to_local_follows_a_scrolled_ancestor) {
|
|
379
|
+
Tree tree;
|
|
380
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
381
|
+
RnWin32View *scroller = tree.box(10, 0, 0, 200, 200);
|
|
382
|
+
RnWin32View *row = tree.box(20, 0, 300, 200, 50);
|
|
383
|
+
root->insertChild(scroller, 0);
|
|
384
|
+
scroller->insertChild(row, 0);
|
|
385
|
+
scroller->setScrollOffset(0.0f, 280.0f);
|
|
386
|
+
|
|
387
|
+
float x = 0.0f;
|
|
388
|
+
float y = 0.0f;
|
|
389
|
+
// The row starts at 300 in content space and the view is scrolled 280, so it
|
|
390
|
+
// is drawn 20 down -- and a press there is at the row's own top.
|
|
391
|
+
EXPECT(row->pageToLocal(root, 10.0f, 20.0f, x, y));
|
|
392
|
+
EXPECT_NEAR(x, 10.0f, 0.001f);
|
|
393
|
+
EXPECT_NEAR(y, 0.0f, 0.001f);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
TEST(page_to_local_refuses_a_chain_with_no_inverse) {
|
|
397
|
+
Tree tree;
|
|
398
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
399
|
+
RnWin32View *flat = tree.box(10, 0, 0, 100, 100);
|
|
400
|
+
const float flattened[16] = {
|
|
401
|
+
0, 0, 0, 0, //
|
|
402
|
+
0, 0, 0, 0, //
|
|
403
|
+
0, 0, 1, 0, //
|
|
404
|
+
0, 0, 0, 1};
|
|
405
|
+
flat->setTransform(flattened);
|
|
406
|
+
root->insertChild(flat, 0);
|
|
407
|
+
|
|
408
|
+
float x = -1.0f;
|
|
409
|
+
float y = -1.0f;
|
|
410
|
+
EXPECT(!flat->pageToLocal(root, 10.0f, 10.0f, x, y));
|
|
411
|
+
// Untouched, so the caller's fallback is what is used.
|
|
412
|
+
EXPECT_NEAR(x, -1.0f, 0.001f);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// ---------------------------------------------------------------------------
|
|
416
|
+
// `hitSlop`: how far outside its own box a view answers a press.
|
|
417
|
+
//
|
|
418
|
+
// The same four questions tests/test_hittest.cpp asks of GTK, in the same
|
|
419
|
+
// order, and asserted the same way: through the hit test every press goes
|
|
420
|
+
// through rather than against the stored insets, because a view that remembered
|
|
421
|
+
// its slop and did not widen the test would leave every target the size it was.
|
|
422
|
+
// ---------------------------------------------------------------------------
|
|
423
|
+
|
|
424
|
+
TEST(hit_slop_grows_the_target_outside_the_box) {
|
|
425
|
+
Tree tree;
|
|
426
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
427
|
+
RnWin32View *icon = tree.box(60, 100, 100, 20, 20);
|
|
428
|
+
root->insertChild(icon, 0);
|
|
429
|
+
const float insets[4] = {12.0f, 12.0f, 12.0f, 12.0f};
|
|
430
|
+
icon->setHitSlop(insets);
|
|
431
|
+
|
|
432
|
+
// Inside the box, as before.
|
|
433
|
+
EXPECT_EQ(tagAt(root, 110, 110), 60);
|
|
434
|
+
// Ten points outside it on each side, which is inside the slop.
|
|
435
|
+
EXPECT_EQ(tagAt(root, 92, 110), 60);
|
|
436
|
+
EXPECT_EQ(tagAt(root, 128, 110), 60);
|
|
437
|
+
EXPECT_EQ(tagAt(root, 110, 92), 60);
|
|
438
|
+
EXPECT_EQ(tagAt(root, 110, 128), 60);
|
|
439
|
+
// And past it, which is the root again.
|
|
440
|
+
EXPECT_EQ(tagAt(root, 80, 110), 1);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Each edge on its own, because four numbers in one struct is four chances to
|
|
444
|
+
// read one into the wrong side: a slop that grew the top when the app asked for
|
|
445
|
+
// the bottom would pass any symmetric test.
|
|
446
|
+
TEST(hit_slop_applies_each_edge_where_it_was_asked_for) {
|
|
447
|
+
Tree tree;
|
|
448
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
449
|
+
RnWin32View *view = tree.box(61, 100, 100, 20, 20);
|
|
450
|
+
root->insertChild(view, 0);
|
|
451
|
+
|
|
452
|
+
const float top[4] = {15.0f, 0.0f, 0.0f, 0.0f};
|
|
453
|
+
view->setHitSlop(top);
|
|
454
|
+
EXPECT_EQ(tagAt(root, 110, 90), 61);
|
|
455
|
+
// Not the other three.
|
|
456
|
+
EXPECT_EQ(tagAt(root, 110, 130), 1);
|
|
457
|
+
EXPECT_EQ(tagAt(root, 90, 110), 1);
|
|
458
|
+
EXPECT_EQ(tagAt(root, 130, 110), 1);
|
|
459
|
+
|
|
460
|
+
// And the opposite edge, through the same view: the prop can change.
|
|
461
|
+
const float bottom[4] = {0.0f, 0.0f, 15.0f, 0.0f};
|
|
462
|
+
view->setHitSlop(bottom);
|
|
463
|
+
EXPECT_EQ(tagAt(root, 110, 130), 61);
|
|
464
|
+
EXPECT_EQ(tagAt(root, 110, 90), 1);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Taken away again. A slop left behind is a view that swallows presses meant for
|
|
468
|
+
// its neighbour, which is harder to notice than a target that is too small.
|
|
469
|
+
TEST(hit_slop_can_be_taken_away_again) {
|
|
470
|
+
Tree tree;
|
|
471
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
472
|
+
RnWin32View *view = tree.box(62, 100, 100, 20, 20);
|
|
473
|
+
root->insertChild(view, 0);
|
|
474
|
+
|
|
475
|
+
const float insets[4] = {12.0f, 12.0f, 12.0f, 12.0f};
|
|
476
|
+
view->setHitSlop(insets);
|
|
477
|
+
EXPECT_EQ(tagAt(root, 92, 110), 62);
|
|
478
|
+
|
|
479
|
+
view->setHitSlop(nullptr);
|
|
480
|
+
EXPECT_EQ(tagAt(root, 92, 110), 1);
|
|
481
|
+
EXPECT_EQ(tagAt(root, 110, 110), 62);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// A slop that overlaps a sibling does not win over it: the sibling is drawn on
|
|
485
|
+
// top, and a target that reached under something visible would take presses
|
|
486
|
+
// meant for it.
|
|
487
|
+
TEST(hit_slop_does_not_beat_a_view_drawn_over_it) {
|
|
488
|
+
Tree tree;
|
|
489
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
490
|
+
RnWin32View *first = tree.box(63, 100, 100, 20, 20);
|
|
491
|
+
root->insertChild(first, 0);
|
|
492
|
+
const float insets[4] = {0.0f, 40.0f, 0.0f, 0.0f};
|
|
493
|
+
first->setHitSlop(insets);
|
|
494
|
+
|
|
495
|
+
RnWin32View *second = tree.box(64, 130, 100, 20, 20);
|
|
496
|
+
root->insertChild(second, 1);
|
|
497
|
+
|
|
498
|
+
// Inside the slop and outside the sibling: the slop answers.
|
|
499
|
+
EXPECT_EQ(tagAt(root, 125, 110), 63);
|
|
500
|
+
// Inside both: the sibling, which is on top.
|
|
501
|
+
EXPECT_EQ(tagAt(root, 140, 110), 64);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// And it is in the tree dump, which is the only way to see it: a view with a
|
|
505
|
+
// bigger target is drawn exactly like one without. Spelled as the other two
|
|
506
|
+
// hosts spell it, because the dump is diffed across the three.
|
|
507
|
+
TEST(hit_slop_is_reported_in_the_tree) {
|
|
508
|
+
Tree tree;
|
|
509
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
510
|
+
RnWin32View *view = tree.box(65, 100, 100, 20, 20);
|
|
511
|
+
root->insertChild(view, 0);
|
|
512
|
+
|
|
513
|
+
EXPECT(root->describeTree().find("hit-slop=") == std::string::npos);
|
|
514
|
+
|
|
515
|
+
const float insets[4] = {16.0f, 8.0f, 4.0f, 2.0f};
|
|
516
|
+
view->setHitSlop(insets);
|
|
517
|
+
EXPECT(root->describeTree().find("hit-slop=(16,8,4,2)") != std::string::npos);
|
|
518
|
+
|
|
519
|
+
// Zeroes are not a slop, so nothing is printed for a view that asked for
|
|
520
|
+
// none: the other two hosts print nothing there either.
|
|
521
|
+
const float none[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
522
|
+
view->setHitSlop(none);
|
|
523
|
+
EXPECT(root->describeTree().find("hit-slop=") == std::string::npos);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// ---------------------------------------------------------------------------
|
|
527
|
+
// The `cursor` style property, which is a hit-test question on this platform.
|
|
528
|
+
//
|
|
529
|
+
// Win32 has no per-view cursor: the window answers `WM_SETCURSOR` with whatever
|
|
530
|
+
// is under the pointer, so "which cursor" is "which view", and that is what
|
|
531
|
+
// these ask. The GTK and AppKit hosts hand the keyword to their toolkit and get
|
|
532
|
+
// the inheritance for free, which is why this is the only suite that has to say
|
|
533
|
+
// what inheritance means.
|
|
534
|
+
// ---------------------------------------------------------------------------
|
|
535
|
+
|
|
536
|
+
TEST(cursor_is_the_innermost_view_that_asked_for_one) {
|
|
537
|
+
Tree tree;
|
|
538
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
539
|
+
RnWin32View *button = tree.box(2, 50, 50, 100, 100);
|
|
540
|
+
RnWin32View *label = tree.box(3, 10, 10, 20, 20);
|
|
541
|
+
button->setCursor("pointer");
|
|
542
|
+
label->setCursor("text");
|
|
543
|
+
button->insertChild(label, 0);
|
|
544
|
+
root->insertChild(button, 0);
|
|
545
|
+
|
|
546
|
+
// Over the label: its own keyword.
|
|
547
|
+
EXPECT_EQ(basalt::win32::cursorNameAt(root, 70, 70), std::string("text"));
|
|
548
|
+
// Over the button but not the label: the button's.
|
|
549
|
+
EXPECT_EQ(basalt::win32::cursorNameAt(root, 120, 120), std::string("pointer"));
|
|
550
|
+
// Over neither: nothing, which is what leaves the pointer alone.
|
|
551
|
+
EXPECT(basalt::win32::cursorNameAt(root, 300, 300).empty());
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// CSS's cursor inherits, so a child that asked for nothing shows its parent's.
|
|
555
|
+
// A host that only read the view under the pointer would answer nothing here,
|
|
556
|
+
// which is an arrow over the middle of a button.
|
|
557
|
+
TEST(cursor_is_inherited_by_a_child_that_asked_for_nothing) {
|
|
558
|
+
Tree tree;
|
|
559
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
560
|
+
RnWin32View *button = tree.box(2, 50, 50, 100, 100);
|
|
561
|
+
RnWin32View *label = tree.box(3, 10, 10, 20, 20);
|
|
562
|
+
button->setCursor("pointer");
|
|
563
|
+
button->insertChild(label, 0);
|
|
564
|
+
root->insertChild(button, 0);
|
|
565
|
+
|
|
566
|
+
EXPECT_EQ(basalt::win32::cursorNameAt(root, 70, 70), std::string("pointer"));
|
|
567
|
+
|
|
568
|
+
// And taken away again: `cursor: 'auto'` arrives as no keyword at all.
|
|
569
|
+
button->setCursor(nullptr);
|
|
570
|
+
EXPECT(basalt::win32::cursorNameAt(root, 70, 70).empty());
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// A view a press passes through does not change the pointer either, which falls
|
|
574
|
+
// out of using the same hit test for both and is worth pinning: the two must
|
|
575
|
+
// not disagree about what the pointer is over.
|
|
576
|
+
TEST(cursor_follows_pointer_events_like_a_press_does) {
|
|
577
|
+
Tree tree;
|
|
578
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
579
|
+
RnWin32View *under = tree.box(2, 50, 50, 100, 100);
|
|
580
|
+
RnWin32View *over = tree.box(3, 50, 50, 100, 100);
|
|
581
|
+
under->setCursor("pointer");
|
|
582
|
+
over->setCursor("text");
|
|
583
|
+
over->setPointerEvents(RnWin32View::PointerEvents::None);
|
|
584
|
+
root->insertChild(under, 0);
|
|
585
|
+
root->insertChild(over, 1);
|
|
586
|
+
|
|
587
|
+
EXPECT_EQ(basalt::win32::cursorNameAt(root, 70, 70), std::string("pointer"));
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
TEST(cursor_is_reported_in_the_tree) {
|
|
591
|
+
Tree tree;
|
|
592
|
+
RnWin32View *root = tree.box(1, 0, 0, 400, 400);
|
|
593
|
+
EXPECT(root->describeTree().find("cursor=") == std::string::npos);
|
|
594
|
+
|
|
595
|
+
root->setCursor("ns-resize");
|
|
596
|
+
// The keyword, as the other two hosts print it: one end-to-end scenario reads
|
|
597
|
+
// this line on all three, and the hyphenated name is where a mapping table
|
|
598
|
+
// goes wrong.
|
|
599
|
+
EXPECT(root->describeTree().find("cursor=ns-resize") != std::string::npos);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// And the Win32 half: a keyword as a cursor the system has.
|
|
603
|
+
//
|
|
604
|
+
// Against `LoadCursorW` rather than a table of names, because the table is the
|
|
605
|
+
// thing under test: a keyword that landed on a cursor the system does not have
|
|
606
|
+
// answers null, and the assertions below would pass on an empty map if they
|
|
607
|
+
// only checked that nothing crashed. So each group is also compared against the
|
|
608
|
+
// others, which is what says `ns-resize` and `ew-resize` are not the same arrow.
|
|
609
|
+
TEST(cursor_keywords_map_onto_the_cursors_windows_has) {
|
|
610
|
+
const auto loaded = [](const char *keyword) {
|
|
611
|
+
return basalt::win32::win32CursorFor(keyword).cursor;
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
EXPECT(loaded("default") == LoadCursorW(nullptr, IDC_ARROW));
|
|
615
|
+
EXPECT(loaded("pointer") == LoadCursorW(nullptr, IDC_HAND));
|
|
616
|
+
EXPECT(loaded("text") == LoadCursorW(nullptr, IDC_IBEAM));
|
|
617
|
+
EXPECT(loaded("crosshair") == LoadCursorW(nullptr, IDC_CROSS));
|
|
618
|
+
EXPECT(loaded("wait") == LoadCursorW(nullptr, IDC_WAIT));
|
|
619
|
+
EXPECT(loaded("progress") == LoadCursorW(nullptr, IDC_APPSTARTING));
|
|
620
|
+
EXPECT(loaded("help") == LoadCursorW(nullptr, IDC_HELP));
|
|
621
|
+
EXPECT(loaded("not-allowed") == LoadCursorW(nullptr, IDC_NO));
|
|
622
|
+
|
|
623
|
+
// The families, which share a cursor because Windows draws no difference --
|
|
624
|
+
// and the eight resize keywords collapse onto four arrows the way a window's
|
|
625
|
+
// own edges do.
|
|
626
|
+
EXPECT(loaded("grab") == loaded("move"));
|
|
627
|
+
EXPECT(loaded("grabbing") == loaded("all-scroll"));
|
|
628
|
+
EXPECT(loaded("ew-resize") == loaded("col-resize"));
|
|
629
|
+
EXPECT(loaded("ns-resize") == loaded("row-resize"));
|
|
630
|
+
EXPECT(loaded("ne-resize") == loaded("nesw-resize"));
|
|
631
|
+
EXPECT(loaded("nw-resize") == loaded("nwse-resize"));
|
|
632
|
+
|
|
633
|
+
// And the four resize arrows are four different arrows.
|
|
634
|
+
EXPECT(loaded("ew-resize") != loaded("ns-resize"));
|
|
635
|
+
EXPECT(loaded("nesw-resize") != loaded("nwse-resize"));
|
|
636
|
+
EXPECT(loaded("ew-resize") != loaded("nesw-resize"));
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
TEST(cursor_none_hides_the_pointer_rather_than_loading_one) {
|
|
640
|
+
const basalt::win32::Win32Cursor none = basalt::win32::win32CursorFor("none");
|
|
641
|
+
EXPECT(none.hidden);
|
|
642
|
+
EXPECT(none.cursor == nullptr);
|
|
643
|
+
|
|
644
|
+
// No keyword at all is neither: the pointer is left as it is.
|
|
645
|
+
const basalt::win32::Win32Cursor nothing = basalt::win32::win32CursorFor("");
|
|
646
|
+
EXPECT(!nothing.hidden);
|
|
647
|
+
EXPECT(nothing.cursor == nullptr);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// The keywords the stock set has nothing for, which this platform draws: see
|
|
651
|
+
// Win32Cursors.cpp. They used to answer null, which left the pointer an arrow
|
|
652
|
+
// and made `cursor` look unimplemented for six of its values.
|
|
653
|
+
//
|
|
654
|
+
// Each is a cursor of its own rather than a shared one, which is the claim
|
|
655
|
+
// that fails if the drawing stops happening -- a null answer, or one cursor
|
|
656
|
+
// handed back for all six, would both show up here. What they *look* like is
|
|
657
|
+
// not assertable from a handle, and nobody has seen them on a screen yet;
|
|
658
|
+
// backlog/platform-windows.md says so.
|
|
659
|
+
TEST(cursor_a_keyword_windows_has_nothing_for_is_drawn_here) {
|
|
660
|
+
std::vector<HCURSOR> drawn;
|
|
661
|
+
for (const char *keyword : {"alias", "cell", "context-menu", "copy", "zoom-in", "zoom-out"}) {
|
|
662
|
+
const basalt::win32::Win32Cursor answer = basalt::win32::win32CursorFor(keyword);
|
|
663
|
+
EXPECT(answer.cursor != nullptr);
|
|
664
|
+
EXPECT(!answer.hidden);
|
|
665
|
+
EXPECT(answer.cursor != LoadCursorW(nullptr, IDC_ARROW));
|
|
666
|
+
for (HCURSOR other : drawn) {
|
|
667
|
+
EXPECT(answer.cursor != other);
|
|
668
|
+
}
|
|
669
|
+
drawn.push_back(answer.cursor);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Asked for twice, answered with the same handle: these are made once and
|
|
673
|
+
// kept, where a stock cursor is looked up in a table the system owns. A
|
|
674
|
+
// cursor made per call would leak one per mouse move.
|
|
675
|
+
EXPECT(basalt::win32::win32CursorFor("zoom-in").cursor
|
|
676
|
+
== basalt::win32::win32CursorFor("zoom-in").cursor);
|
|
677
|
+
|
|
678
|
+
// And a keyword that is neither stock nor drawn still leaves the pointer
|
|
679
|
+
// alone, which is what an unknown value has to do.
|
|
680
|
+
EXPECT(basalt::win32::win32CursorFor("not-a-cursor").cursor == nullptr);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// ---------------------------------------------------------------------------
|
|
684
|
+
// `keyboardType` as a Windows input scope. See Win32InputScopes.h.
|
|
685
|
+
//
|
|
686
|
+
// A table, so a table test: what is worth asserting is that each name lands on
|
|
687
|
+
// the scope that means the same thing, and that the ones Windows draws no
|
|
688
|
+
// distinction between are the ones that share a scope. Here rather than in the
|
|
689
|
+
// <TextInput> suite because the mapping carries no React Native, which is also
|
|
690
|
+
// why it is keyed on the name core prints rather than on the enum.
|
|
691
|
+
// ---------------------------------------------------------------------------
|
|
692
|
+
|
|
693
|
+
TEST(input_scope_follows_the_keyboard_type_a_field_asked_for) {
|
|
694
|
+
const auto scope = [](const char *name) {
|
|
695
|
+
return basalt::win32::inputScopeForKeyboardType(name);
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
EXPECT_EQ(static_cast<int>(scope("email-address")),
|
|
699
|
+
static_cast<int>(IS_EMAIL_SMTPEMAILADDRESS));
|
|
700
|
+
EXPECT_EQ(static_cast<int>(scope("url")), static_cast<int>(IS_URL));
|
|
701
|
+
EXPECT_EQ(static_cast<int>(scope("phone-pad")),
|
|
702
|
+
static_cast<int>(IS_TELEPHONE_FULLTELEPHONENUMBER));
|
|
703
|
+
// A number that may carry a separator against digits and nothing else, which
|
|
704
|
+
// is the one distinction in this table that is not just a rename.
|
|
705
|
+
EXPECT_EQ(static_cast<int>(scope("numeric")), static_cast<int>(IS_NUMBER));
|
|
706
|
+
EXPECT_EQ(static_cast<int>(scope("decimal-pad")), static_cast<int>(IS_NUMBER));
|
|
707
|
+
EXPECT_EQ(static_cast<int>(scope("number-pad")), static_cast<int>(IS_DIGITS));
|
|
708
|
+
EXPECT(scope("numeric") != scope("number-pad"));
|
|
709
|
+
|
|
710
|
+
// `default` and the names that are iOS's own vocabulary: ordinary text, which
|
|
711
|
+
// is more useful than refusing to answer and is what the comment there says.
|
|
712
|
+
EXPECT_EQ(static_cast<int>(scope("default")), static_cast<int>(IS_DEFAULT));
|
|
713
|
+
EXPECT_EQ(static_cast<int>(scope("twitter")), static_cast<int>(IS_DEFAULT));
|
|
714
|
+
EXPECT_EQ(static_cast<int>(scope("ascii-capable")), static_cast<int>(IS_DEFAULT));
|
|
715
|
+
// And a name this table has never heard of, which is what a future React
|
|
716
|
+
// Native adding one looks like.
|
|
717
|
+
EXPECT_EQ(static_cast<int>(scope("something-new")), static_cast<int>(IS_DEFAULT));
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// What a field asked for about its text reaches the tree dump, which is where
|
|
721
|
+
// an app can see that a prop arrived on a host that cannot act on it -- and
|
|
722
|
+
// what the end-to-end scenario reads on all three.
|
|
723
|
+
TEST(input_kinds_and_text_checking_are_reported_in_the_tree) {
|
|
724
|
+
Tree tree;
|
|
725
|
+
RnWin32View *view = tree.box(1, 0, 0, 100, 40);
|
|
726
|
+
|
|
727
|
+
// Nothing said: nothing printed. Unset spelling is a third state rather than
|
|
728
|
+
// off, and a host that resolved it to false would mark every field.
|
|
729
|
+
EXPECT(view->describeTree().find("spellcheck=") == std::string::npos);
|
|
730
|
+
EXPECT(view->describeTree().find("autocorrect=") == std::string::npos);
|
|
731
|
+
EXPECT(view->describeTree().find("keyboard=") == std::string::npos);
|
|
732
|
+
|
|
733
|
+
view->setTextChecking("off", "off");
|
|
734
|
+
view->setInputKinds("none", "email-address");
|
|
735
|
+
const std::string dump = view->describeTree();
|
|
736
|
+
EXPECT(dump.find("spellcheck=off") != std::string::npos);
|
|
737
|
+
EXPECT(dump.find("autocorrect=off") != std::string::npos);
|
|
738
|
+
EXPECT(dump.find("autocapitalize=none") != std::string::npos);
|
|
739
|
+
EXPECT(dump.find("keyboard=email-address") != std::string::npos);
|
|
740
|
+
|
|
741
|
+
// And taken away again, which is what a field that stops saying anything
|
|
742
|
+
// means: core answers a null name for an unset flag.
|
|
743
|
+
view->setTextChecking(nullptr, nullptr);
|
|
744
|
+
const std::string quiet = view->describeTree();
|
|
745
|
+
EXPECT(quiet.find("spellcheck=") == std::string::npos);
|
|
746
|
+
EXPECT(quiet.find("autocorrect=") == std::string::npos);
|
|
747
|
+
// The other two are plain enums with React Native's own defaults, so they
|
|
748
|
+
// stay: a field always has a capitalisation and a keyboard.
|
|
749
|
+
EXPECT(quiet.find("autocapitalize=none") != std::string::npos);
|
|
750
|
+
}
|