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.
- package/LICENSE +21 -0
- package/cli/runWindows.js +35 -0
- package/cli/shared.js +75 -0
- package/native/CMakeLists.txt +398 -0
- package/native/tests/test_win32_accessibility.cpp +271 -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 +410 -0
- package/native/tests/test_win32_image.cpp +239 -0
- package/native/tests/test_win32_imageloader.cpp +208 -0
- package/native/tests/test_win32_input.cpp +361 -0
- package/native/tests/test_win32_key_events.cpp +84 -0
- package/native/tests/test_win32_mounting.cpp +410 -0
- package/native/tests/test_win32_paint.cpp +505 -0
- package/native/tests/test_win32_scrollview.cpp +385 -0
- package/native/tests/test_win32_text.cpp +258 -0
- package/native/tests/test_win32_textinput.cpp +609 -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 +124 -0
- package/native/win32/DirectWriteLayout.cpp +116 -0
- package/native/win32/DirectWriteLayout.h +36 -0
- package/native/win32/DirectWriteLayoutManager.cpp +124 -0
- package/native/win32/FontRegistryDirectWrite.cpp +117 -0
- package/native/win32/PlatformServicesWin32.cpp +643 -0
- package/native/win32/RnWin32Accessible.cpp +258 -0
- package/native/win32/RnWin32Accessible.h +91 -0
- package/native/win32/RnWin32Image.cpp +265 -0
- package/native/win32/RnWin32Image.h +93 -0
- package/native/win32/RnWin32TextLayout.cpp +310 -0
- package/native/win32/RnWin32TextLayout.h +160 -0
- package/native/win32/RnWin32View.cpp +1281 -0
- package/native/win32/RnWin32View.h +506 -0
- package/native/win32/Win32AnimationChoreographer.cpp +76 -0
- package/native/win32/Win32AnimationChoreographer.h +61 -0
- package/native/win32/Win32Clip.h +173 -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 +167 -0
- package/native/win32/Win32ImageLoader.h +104 -0
- package/native/win32/Win32KeyEvents.cpp +126 -0
- package/native/win32/Win32KeyEvents.h +20 -0
- package/native/win32/Win32MenuBar.cpp +352 -0
- package/native/win32/Win32MenuBar.h +16 -0
- package/native/win32/Win32MountingManager.cpp +727 -0
- package/native/win32/Win32MountingManager.h +203 -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 +515 -0
- package/native/win32/Win32ScrollView.h +208 -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 +811 -0
- package/native/win32/Win32TextInput.h +232 -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 +443 -0
- package/native/win32/Win32TouchDispatcher.h +153 -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 +2481 -0
- package/native/win32/mount_harness_win32.cpp +316 -0
- package/package.json +26 -0
- package/react-native.config.js +22 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// The geometry of a hidden title bar: how big the caption is, which button a
|
|
2
|
+
// point is over, and whether a point drags the window.
|
|
3
|
+
//
|
|
4
|
+
// Pure functions of a view tree and a size, so no window, no DWM and no React
|
|
5
|
+
// Native. These are the questions a custom caption gets wrong in ways a person
|
|
6
|
+
// notices at once -- a close button a few pixels off, a label that will not
|
|
7
|
+
// drag, a button that drags instead of pressing -- and a test does not, unless
|
|
8
|
+
// it asks them.
|
|
9
|
+
|
|
10
|
+
#include "TestHarness.h"
|
|
11
|
+
|
|
12
|
+
#include "RnWin32View.h"
|
|
13
|
+
#include "Win32TitleBarLayout.h"
|
|
14
|
+
|
|
15
|
+
#include <memory>
|
|
16
|
+
#include <sstream>
|
|
17
|
+
#include <string>
|
|
18
|
+
#include <vector>
|
|
19
|
+
|
|
20
|
+
using basalt::win32::CaptionButton;
|
|
21
|
+
using basalt::win32::captionButtonAt;
|
|
22
|
+
using basalt::win32::captionMetricsForDpi;
|
|
23
|
+
using basalt::win32::isTitleBarDragRegionAt;
|
|
24
|
+
using basalt::win32::kTitleBarDragRegionId;
|
|
25
|
+
using basalt::win32::kTitleBarNoDragRegionId;
|
|
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, const char *nativeId = "") {
|
|
33
|
+
auto view = std::make_unique<RnWin32View>(tag);
|
|
34
|
+
view->setFrame(x, y, w, h);
|
|
35
|
+
view->setNativeId(nativeId);
|
|
36
|
+
RnWin32View *raw = view.get();
|
|
37
|
+
views_.push_back(std::move(view));
|
|
38
|
+
return raw;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private:
|
|
42
|
+
std::vector<std::unique_ptr<RnWin32View>> views_;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
} // namespace
|
|
46
|
+
|
|
47
|
+
TEST(caption_metrics_are_windows_elevens_and_scale_with_dpi) {
|
|
48
|
+
const auto at100 = captionMetricsForDpi(96);
|
|
49
|
+
EXPECT_NEAR(at100.height, 32.0f, 0.01f);
|
|
50
|
+
EXPECT_NEAR(at100.buttonWidth, 46.0f, 0.01f);
|
|
51
|
+
|
|
52
|
+
const auto at150 = captionMetricsForDpi(144);
|
|
53
|
+
EXPECT_NEAR(at150.height, 48.0f, 0.01f);
|
|
54
|
+
EXPECT_NEAR(at150.buttonWidth, 69.0f, 0.01f);
|
|
55
|
+
|
|
56
|
+
// A DPI of zero is what a window that has not been shown can report; it is
|
|
57
|
+
// treated as 96 rather than collapsing the caption to nothing.
|
|
58
|
+
EXPECT_NEAR(captionMetricsForDpi(0).height, 32.0f, 0.01f);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
TEST(caption_buttons_sit_flush_right_in_the_order_windows_puts_them) {
|
|
62
|
+
const auto metrics = captionMetricsForDpi(96);
|
|
63
|
+
const float width = 800;
|
|
64
|
+
|
|
65
|
+
EXPECT(captionButtonAt(799, 10, width, metrics) == CaptionButton::Close);
|
|
66
|
+
EXPECT(captionButtonAt(width - 46 - 1, 10, width, metrics) == CaptionButton::Maximize);
|
|
67
|
+
EXPECT(captionButtonAt(width - 92 - 1, 10, width, metrics) == CaptionButton::Minimize);
|
|
68
|
+
EXPECT(captionButtonAt(width - 138 - 1, 10, width, metrics) == CaptionButton::None);
|
|
69
|
+
|
|
70
|
+
// Below the caption, or outside the window, is no button at all.
|
|
71
|
+
EXPECT(captionButtonAt(799, 32, width, metrics) == CaptionButton::None);
|
|
72
|
+
EXPECT(captionButtonAt(-1, 10, width, metrics) == CaptionButton::None);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
TEST(a_label_inside_a_drag_region_drags_the_window) {
|
|
76
|
+
Tree tree;
|
|
77
|
+
RnWin32View *root = tree.box(1, 0, 0, 800, 600);
|
|
78
|
+
RnWin32View *header = tree.box(2, 0, 0, 800, 32, kTitleBarDragRegionId);
|
|
79
|
+
root->insertChild(header, 0);
|
|
80
|
+
header->insertChild(tree.box(3, 10, 5, 100, 20), 0);
|
|
81
|
+
|
|
82
|
+
EXPECT(isTitleBarDragRegionAt(root, 20, 10));
|
|
83
|
+
EXPECT(isTitleBarDragRegionAt(root, 400, 10));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
TEST(a_no_drag_region_inside_a_drag_region_stays_pressable) {
|
|
87
|
+
Tree tree;
|
|
88
|
+
RnWin32View *root = tree.box(1, 0, 0, 800, 600);
|
|
89
|
+
RnWin32View *header = tree.box(2, 0, 0, 800, 32, kTitleBarDragRegionId);
|
|
90
|
+
root->insertChild(header, 0);
|
|
91
|
+
RnWin32View *button = tree.box(3, 600, 0, 50, 32, kTitleBarNoDragRegionId);
|
|
92
|
+
header->insertChild(button, 0);
|
|
93
|
+
|
|
94
|
+
EXPECT(!isTitleBarDragRegionAt(root, 620, 10));
|
|
95
|
+
|
|
96
|
+
// And a drag region nested inside that button drags again: the nearest
|
|
97
|
+
// marker decides, not the outermost.
|
|
98
|
+
button->insertChild(tree.box(4, 5, 5, 10, 10, kTitleBarDragRegionId), 0);
|
|
99
|
+
EXPECT(isTitleBarDragRegionAt(root, 607, 7));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
TEST(a_point_under_nothing_marked_is_ordinary_client_area) {
|
|
103
|
+
Tree tree;
|
|
104
|
+
RnWin32View *root = tree.box(1, 0, 0, 800, 600);
|
|
105
|
+
root->insertChild(tree.box(2, 0, 0, 800, 32, kTitleBarDragRegionId), 0);
|
|
106
|
+
root->insertChild(tree.box(3, 0, 32, 800, 568), 1);
|
|
107
|
+
|
|
108
|
+
EXPECT(!isTitleBarDragRegionAt(root, 400, 300));
|
|
109
|
+
// A miss altogether -- outside the root -- is not a drag either.
|
|
110
|
+
EXPECT(!isTitleBarDragRegionAt(root, 900, 10));
|
|
111
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// The caption's four actions before there is a window.
|
|
2
|
+
//
|
|
3
|
+
// minimize, toggleMaximize, close and startWindowDrag are what an app-drawn
|
|
4
|
+
// header asks for by name. GtkWindowModule and AppKitWindowModule have
|
|
5
|
+
// registered them all along; this host registered none of them, so on Windows
|
|
6
|
+
// useWindow().minimize() and .close() were silent no-ops -- and useCloseRequest
|
|
7
|
+
// could refuse a close and then find that agreeing to it did nothing either.
|
|
8
|
+
//
|
|
9
|
+
// Now that they are registered, the question this asks is the one a test can
|
|
10
|
+
// answer without a desktop: what they do before the host has an HWND. An app
|
|
11
|
+
// can call close() from its first render, and the honest answer then is to do
|
|
12
|
+
// nothing, not to dereference a null window.
|
|
13
|
+
//
|
|
14
|
+
// What this deliberately cannot check is the other half -- that a real window
|
|
15
|
+
// actually minimises, or that startDrag hands Windows its move loop. Both need
|
|
16
|
+
// a window, a message queue and a person's mouse; they belong to the
|
|
17
|
+
// end-to-end suite, and saying so here is better than a test that looks like
|
|
18
|
+
// it covers them.
|
|
19
|
+
//
|
|
20
|
+
// Lives in the React-Native-gated source list rather than beside
|
|
21
|
+
// test_win32_titlebar.cpp, because Win32TitleBar is compiled into
|
|
22
|
+
// basalt_win32_mounting, which only exists when basalt_core does. Its
|
|
23
|
+
// neighbour tests pure geometry and needs no such thing.
|
|
24
|
+
|
|
25
|
+
#include "TestHarness.h"
|
|
26
|
+
|
|
27
|
+
#include "Win32TitleBar.h"
|
|
28
|
+
|
|
29
|
+
using basalt::TitleBarStyle;
|
|
30
|
+
using basalt::Win32TitleBar;
|
|
31
|
+
|
|
32
|
+
TEST(caption_actions_do_nothing_when_no_window_is_attached) {
|
|
33
|
+
Win32TitleBar bar;
|
|
34
|
+
const TitleBarStyle before = bar.style();
|
|
35
|
+
|
|
36
|
+
bar.minimize();
|
|
37
|
+
bar.toggleMaximize();
|
|
38
|
+
bar.close();
|
|
39
|
+
bar.startDrag();
|
|
40
|
+
|
|
41
|
+
// Reaching this line is most of the assertion: each call returned rather
|
|
42
|
+
// than posting to a null window. The style is the one piece of state they
|
|
43
|
+
// could have disturbed on the way, so it is worth saying it did not move.
|
|
44
|
+
EXPECT(bar.style() == before);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
TEST(caption_actions_are_repeatable_without_a_window) {
|
|
48
|
+
Win32TitleBar bar;
|
|
49
|
+
|
|
50
|
+
// Twice, because the guard is a plain early return and an app that polls --
|
|
51
|
+
// a header re-rendering on every frame, say -- makes exactly this call
|
|
52
|
+
// pattern. Nothing here accumulates state, and this is what would notice if
|
|
53
|
+
// one of them started to.
|
|
54
|
+
for (int attempt = 0; attempt < 2; ++attempt) {
|
|
55
|
+
bar.minimize();
|
|
56
|
+
bar.toggleMaximize();
|
|
57
|
+
bar.close();
|
|
58
|
+
bar.startDrag();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
EXPECT(bar.style() == TitleBarStyle::Native);
|
|
62
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
// Tests for the Windows view layer.
|
|
2
|
+
//
|
|
3
|
+
// RnWin32View has no React Native dependency and no Direct2D dependency until
|
|
4
|
+
// something paints, so these need no Fabric machinery, no window and no
|
|
5
|
+
// display. They share the toolkit-free harness the GTK and macOS suites use.
|
|
6
|
+
//
|
|
7
|
+
// The questions are deliberately the same ones tests/test_view.cpp asks of GTK
|
|
8
|
+
// and tests/test_appkit_view.mm asks of AppKit, in the same order: what is being
|
|
9
|
+
// checked is largely whether three platforms still agree, so a divergence
|
|
10
|
+
// should fail here rather than in an app.
|
|
11
|
+
|
|
12
|
+
#include "TestHarness.h"
|
|
13
|
+
|
|
14
|
+
#include "RnWin32View.h"
|
|
15
|
+
|
|
16
|
+
#include <memory>
|
|
17
|
+
#include <sstream>
|
|
18
|
+
#include <string>
|
|
19
|
+
#include <vector>
|
|
20
|
+
|
|
21
|
+
using basalt::win32::RnWin32View;
|
|
22
|
+
|
|
23
|
+
namespace {
|
|
24
|
+
|
|
25
|
+
// The tests own their views, as the mounting registry will. A view owns neither
|
|
26
|
+
// its children nor its parent; see RnWin32View.h.
|
|
27
|
+
class Tree {
|
|
28
|
+
public:
|
|
29
|
+
RnWin32View *box(int32_t tag, float x, float y, float w, float h) {
|
|
30
|
+
auto view = std::make_unique<RnWin32View>(tag);
|
|
31
|
+
view->setFrame(x, y, w, h);
|
|
32
|
+
RnWin32View *raw = view.get();
|
|
33
|
+
views_.push_back(std::move(view));
|
|
34
|
+
return raw;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Releases one view, the way a Delete mutation would.
|
|
38
|
+
void destroy(RnWin32View *view) {
|
|
39
|
+
for (auto it = views_.begin(); it != views_.end(); ++it) {
|
|
40
|
+
if (it->get() == view) {
|
|
41
|
+
views_.erase(it);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private:
|
|
48
|
+
std::vector<std::unique_ptr<RnWin32View>> views_;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
int32_t tagOfChild(const RnWin32View *parent, size_t index) {
|
|
52
|
+
return parent->children()[index]->tag();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
} // namespace
|
|
56
|
+
|
|
57
|
+
TEST(win32_view_stores_its_tag_and_frame) {
|
|
58
|
+
Tree tree;
|
|
59
|
+
RnWin32View *view = tree.box(42, 12, 34, 100, 50);
|
|
60
|
+
|
|
61
|
+
EXPECT_EQ(view->tag(), 42);
|
|
62
|
+
EXPECT_NEAR(view->frame().x, 12.0, 0.001);
|
|
63
|
+
EXPECT_NEAR(view->frame().y, 34.0, 0.001);
|
|
64
|
+
EXPECT_NEAR(view->frame().width, 100.0, 0.001);
|
|
65
|
+
EXPECT_NEAR(view->frame().height, 50.0, 0.001);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
TEST(win32_children_insert_at_the_requested_index) {
|
|
69
|
+
Tree tree;
|
|
70
|
+
RnWin32View *root = tree.box(1, 0, 0, 200, 200);
|
|
71
|
+
RnWin32View *first = tree.box(2, 0, 0, 10, 10);
|
|
72
|
+
RnWin32View *second = tree.box(3, 0, 0, 10, 10);
|
|
73
|
+
RnWin32View *between = tree.box(4, 0, 0, 10, 10);
|
|
74
|
+
|
|
75
|
+
root->insertChild(first, 0);
|
|
76
|
+
root->insertChild(second, 1);
|
|
77
|
+
// An Insert's index counts positions in the parent's *final* child list, so
|
|
78
|
+
// this lands between the two. An append-only implementation passes every
|
|
79
|
+
// other test in this file.
|
|
80
|
+
root->insertChild(between, 1);
|
|
81
|
+
|
|
82
|
+
EXPECT_EQ(root->children().size(), size_t{3});
|
|
83
|
+
EXPECT_EQ(tagOfChild(root, 0), 2);
|
|
84
|
+
EXPECT_EQ(tagOfChild(root, 1), 4);
|
|
85
|
+
EXPECT_EQ(tagOfChild(root, 2), 3);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
TEST(win32_view_removes_only_its_own_child) {
|
|
89
|
+
Tree tree;
|
|
90
|
+
RnWin32View *root = tree.box(1, 0, 0, 200, 200);
|
|
91
|
+
RnWin32View *mine = tree.box(2, 0, 0, 10, 10);
|
|
92
|
+
RnWin32View *someone_elses = tree.box(3, 0, 0, 10, 10);
|
|
93
|
+
RnWin32View *other = tree.box(4, 0, 0, 200, 200);
|
|
94
|
+
|
|
95
|
+
root->insertChild(mine, 0);
|
|
96
|
+
other->insertChild(someone_elses, 0);
|
|
97
|
+
|
|
98
|
+
root->removeChild(someone_elses);
|
|
99
|
+
|
|
100
|
+
EXPECT_EQ(root->children().size(), size_t{1});
|
|
101
|
+
EXPECT_EQ(other->children().size(), size_t{1});
|
|
102
|
+
EXPECT_EQ(someone_elses->parent()->tag(), 4);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
TEST(win32_remove_detaches_but_does_not_destroy) {
|
|
106
|
+
Tree tree;
|
|
107
|
+
RnWin32View *root = tree.box(1, 0, 0, 200, 200);
|
|
108
|
+
RnWin32View *child = tree.box(2, 5, 5, 10, 10);
|
|
109
|
+
|
|
110
|
+
root->insertChild(child, 0);
|
|
111
|
+
root->removeChild(child);
|
|
112
|
+
|
|
113
|
+
// A reparent is a Remove and an Insert with no Delete between them, so the
|
|
114
|
+
// view has to survive the gap with everything about it intact.
|
|
115
|
+
EXPECT_EQ(root->children().size(), size_t{0});
|
|
116
|
+
EXPECT(child->parent() == nullptr);
|
|
117
|
+
EXPECT_NEAR(child->frame().x, 5.0, 0.001);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
TEST(win32_insert_reparents_without_a_remove) {
|
|
121
|
+
Tree tree;
|
|
122
|
+
RnWin32View *first = tree.box(1, 0, 0, 200, 200);
|
|
123
|
+
RnWin32View *second = tree.box(2, 0, 0, 200, 200);
|
|
124
|
+
RnWin32View *child = tree.box(3, 0, 0, 10, 10);
|
|
125
|
+
|
|
126
|
+
first->insertChild(child, 0);
|
|
127
|
+
second->insertChild(child, 0);
|
|
128
|
+
|
|
129
|
+
EXPECT_EQ(first->children().size(), size_t{0});
|
|
130
|
+
EXPECT_EQ(second->children().size(), size_t{1});
|
|
131
|
+
EXPECT_EQ(child->parent()->tag(), 2);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
TEST(win32_destroying_a_view_leaves_nothing_pointing_at_it) {
|
|
135
|
+
Tree tree;
|
|
136
|
+
RnWin32View *root = tree.box(1, 0, 0, 200, 200);
|
|
137
|
+
RnWin32View *child = tree.box(2, 0, 0, 10, 10);
|
|
138
|
+
RnWin32View *grandchild = tree.box(3, 0, 0, 5, 5);
|
|
139
|
+
|
|
140
|
+
root->insertChild(child, 0);
|
|
141
|
+
child->insertChild(grandchild, 0);
|
|
142
|
+
|
|
143
|
+
tree.destroy(child);
|
|
144
|
+
|
|
145
|
+
// The registry owns every view, so a Delete of a parent does not delete its
|
|
146
|
+
// children -- their own Deletes are separate mutations. What must not survive
|
|
147
|
+
// is a pointer to the freed view, on either side.
|
|
148
|
+
EXPECT_EQ(root->children().size(), size_t{0});
|
|
149
|
+
EXPECT(grandchild->parent() == nullptr);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
TEST(win32_z_index_reorders_painting_and_not_the_child_list) {
|
|
153
|
+
Tree tree;
|
|
154
|
+
RnWin32View *root = tree.box(1, 0, 0, 200, 200);
|
|
155
|
+
RnWin32View *first = tree.box(2, 0, 0, 10, 10);
|
|
156
|
+
RnWin32View *second = tree.box(3, 0, 0, 10, 10);
|
|
157
|
+
|
|
158
|
+
root->insertChild(first, 0);
|
|
159
|
+
root->insertChild(second, 1);
|
|
160
|
+
first->setZIndex(10);
|
|
161
|
+
|
|
162
|
+
// Fabric's Insert and Remove index into this list, so it stays in mutation
|
|
163
|
+
// order no matter what zIndex says. That painting follows zIndex instead is
|
|
164
|
+
// asserted in test_win32_paint.cpp, where it can be seen.
|
|
165
|
+
EXPECT_EQ(tagOfChild(root, 0), 2);
|
|
166
|
+
EXPECT_EQ(tagOfChild(root, 1), 3);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
TEST(win32_scroll_offset_is_reported_in_the_tree) {
|
|
170
|
+
Tree tree;
|
|
171
|
+
RnWin32View *root = tree.box(1, 0, 0, 200, 200);
|
|
172
|
+
root->setScrollOffset(0, 40);
|
|
173
|
+
|
|
174
|
+
const std::string described = root->describeTree();
|
|
175
|
+
EXPECT_EQ(described, std::string("view tag=1 frame=(0,0 200x200) scroll=(0,40)\n"));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
TEST(win32_transform_reports_the_affine_part_of_matrix3d) {
|
|
179
|
+
Tree tree;
|
|
180
|
+
RnWin32View *root = tree.box(1, 0, 0, 200, 200);
|
|
181
|
+
|
|
182
|
+
// A quarter turn with a translation, in CSS matrix3d order, which is column
|
|
183
|
+
// major: elements 0, 1, 4, 5, 12 and 13 are the affine part. Getting the
|
|
184
|
+
// indices wrong is a transpose, and a transpose is invisible on any
|
|
185
|
+
// axis-aligned box -- which is why this asserts on an asymmetric matrix.
|
|
186
|
+
const float matrix[16] = {
|
|
187
|
+
0, 1, 0, 0, //
|
|
188
|
+
-1, 0, 0, 0, //
|
|
189
|
+
0, 0, 1, 0, //
|
|
190
|
+
5, 7, 0, 1};
|
|
191
|
+
root->setTransform(matrix);
|
|
192
|
+
|
|
193
|
+
const std::string described = root->describeTree();
|
|
194
|
+
EXPECT_EQ(described,
|
|
195
|
+
std::string("view tag=1 frame=(0,0 200x200) transform=(0,1,-1,0,5,7)\n"));
|
|
196
|
+
|
|
197
|
+
root->setTransform(nullptr);
|
|
198
|
+
EXPECT_EQ(root->describeTree(), std::string("view tag=1 frame=(0,0 200x200)\n"));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
TEST(win32_view_describes_its_tree_like_the_other_hosts) {
|
|
202
|
+
Tree tree;
|
|
203
|
+
RnWin32View *root = tree.box(1, 0, 0, 640, 420);
|
|
204
|
+
root->setBackgroundColor(0.12f, 0.13f, 0.16f, 1.0f, true);
|
|
205
|
+
|
|
206
|
+
RnWin32View *child = tree.box(2, 32, 32, 240, 160);
|
|
207
|
+
child->setOpacity(0.4f);
|
|
208
|
+
child->setClipsChildren(true);
|
|
209
|
+
root->insertChild(child, 0);
|
|
210
|
+
|
|
211
|
+
// Byte for byte what tests/test_appkit_view.mm expects of AppKit and what
|
|
212
|
+
// gtk/RnView.cpp produces. scripts/compare_hosts.sh diffs these dumps line by
|
|
213
|
+
// line, so this string is the contract rather than a convenience.
|
|
214
|
+
const std::string expected =
|
|
215
|
+
"view tag=1 frame=(0,0 640x420) bg=#1f2129ff\n"
|
|
216
|
+
" view tag=2 frame=(32,32 240x160) opacity=0.4 clip\n";
|
|
217
|
+
EXPECT_EQ(root->describeTree(), expected);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
int main(int argc, char **argv) {
|
|
221
|
+
return basalt::testing::runAllTests(argc, argv);
|
|
222
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Windows' half of core/ColorScheme.h -- light or dark, as the system has it.
|
|
2
|
+
//
|
|
3
|
+
// The answer lives in the registry rather than in an API: Windows exposes the
|
|
4
|
+
// app theme as `AppsUseLightTheme` under the Personalize key, and there is no
|
|
5
|
+
// Win32 function that returns it. WinRT has UISettings, which would be a
|
|
6
|
+
// heavier dependency for one boolean and is unavailable in a plain Win32
|
|
7
|
+
// process without an apartment. The registry value is what every desktop
|
|
8
|
+
// framework reads, and it is what Explorer writes when the setting changes.
|
|
9
|
+
//
|
|
10
|
+
// Note the sense: the value is "apps use *light* theme", so 1 is light and 0 is
|
|
11
|
+
// dark, and an absent value means light -- which is the pre-2018 behaviour and
|
|
12
|
+
// still the right default.
|
|
13
|
+
|
|
14
|
+
#include "ColorScheme.h"
|
|
15
|
+
|
|
16
|
+
#include <windows.h>
|
|
17
|
+
|
|
18
|
+
namespace basalt {
|
|
19
|
+
namespace {
|
|
20
|
+
|
|
21
|
+
constexpr const wchar_t *kPersonalizeKey =
|
|
22
|
+
L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
|
|
23
|
+
constexpr const wchar_t *kAppsUseLightTheme = L"AppsUseLightTheme";
|
|
24
|
+
|
|
25
|
+
} // namespace
|
|
26
|
+
|
|
27
|
+
ColorScheme systemColorScheme() {
|
|
28
|
+
DWORD value = 1;
|
|
29
|
+
DWORD size = sizeof(value);
|
|
30
|
+
const LSTATUS status = RegGetValueW(HKEY_CURRENT_USER,
|
|
31
|
+
kPersonalizeKey,
|
|
32
|
+
kAppsUseLightTheme,
|
|
33
|
+
RRF_RT_REG_DWORD,
|
|
34
|
+
nullptr,
|
|
35
|
+
&value,
|
|
36
|
+
&size);
|
|
37
|
+
if (status != ERROR_SUCCESS) {
|
|
38
|
+
// No value at all: a machine that predates the setting, or a policy that
|
|
39
|
+
// removed it. Light is what those show.
|
|
40
|
+
return ColorScheme::Light;
|
|
41
|
+
}
|
|
42
|
+
return value == 0 ? ColorScheme::Dark : ColorScheme::Light;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
void startObservingColorScheme() {
|
|
46
|
+
// Not implemented, and deliberately not faked. Watching this properly means
|
|
47
|
+
// RegNotifyChangeKeyValue on a worker thread, or handling WM_SETTINGCHANGE
|
|
48
|
+
// with lParam "ImmersiveColorSet" on the host's window -- and the second is
|
|
49
|
+
// the better answer, because it arrives on the thread that would have to
|
|
50
|
+
// repaint. It belongs with the host for that reason.
|
|
51
|
+
//
|
|
52
|
+
// The other two desktops subscribe and, per docs/BACKLOG.md, have never been
|
|
53
|
+
// watched firing. So nothing is lost here that is proven to work there.
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
} // namespace basalt
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Which components this platform claims.
|
|
2
|
+
//
|
|
3
|
+
// `getDefaultComponentRegistryFactory()` is declared by ReactCommon and defined
|
|
4
|
+
// nowhere in it: each host supplies its own and, in doing so, says what it can
|
|
5
|
+
// put on screen. Phase 19 found out the hard way that it cannot be shared --
|
|
6
|
+
// `ParagraphComponentDescriptor` constructs a `TextLayoutManager`, so a
|
|
7
|
+
// platform without a text engine fails to link rather than failing to render
|
|
8
|
+
// text. See `core/ComponentRegistry.h`.
|
|
9
|
+
//
|
|
10
|
+
// The set here must match what `Win32MountingManager::hasComponent` answers
|
|
11
|
+
// for. When they disagree the registry wins, Fabric builds shadow nodes nothing
|
|
12
|
+
// can mount, and the app renders blank rectangles rather than reporting
|
|
13
|
+
// anything -- which is why the two live in the same directory and should be
|
|
14
|
+
// read together.
|
|
15
|
+
|
|
16
|
+
#include "ComponentRegistry.h"
|
|
17
|
+
|
|
18
|
+
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
|
|
19
|
+
#include <react/renderer/components/FBReactNativeSpec/ComponentDescriptors.h>
|
|
20
|
+
#include <react/renderer/components/image/ImageComponentDescriptor.h>
|
|
21
|
+
#include <react/renderer/components/iostextinput/TextInputComponentDescriptor.h>
|
|
22
|
+
#include <react/renderer/components/modal/ModalHostViewComponentDescriptor.h>
|
|
23
|
+
#include <react/renderer/components/scrollview/ScrollViewComponentDescriptor.h>
|
|
24
|
+
#include <react/renderer/components/switch/AppleSwitchComponentDescriptor.h>
|
|
25
|
+
#include <react/renderer/components/text/ParagraphComponentDescriptor.h>
|
|
26
|
+
#include <react/renderer/components/text/RawTextComponentDescriptor.h>
|
|
27
|
+
#include <react/renderer/components/text/TextComponentDescriptor.h>
|
|
28
|
+
#include <react/renderer/components/view/ViewComponentDescriptor.h>
|
|
29
|
+
|
|
30
|
+
namespace facebook::react {
|
|
31
|
+
|
|
32
|
+
ComponentRegistryFactory getDefaultComponentRegistryFactory() {
|
|
33
|
+
return [](const EventDispatcher::Weak &eventDispatcher,
|
|
34
|
+
const std::shared_ptr<const ContextContainer> &contextContainer) {
|
|
35
|
+
static auto providerRegistry = []() {
|
|
36
|
+
auto registry = std::make_shared<ComponentDescriptorProviderRegistry>();
|
|
37
|
+
registry->add(concreteComponentDescriptorProvider<ViewComponentDescriptor>());
|
|
38
|
+
|
|
39
|
+
// Text is three descriptors and only one of them mounts. <Text> becomes a
|
|
40
|
+
// Text node, its string a RawText node, and the outermost <Text> a
|
|
41
|
+
// Paragraph that folds the whole subtree into a single AttributedString.
|
|
42
|
+
registry->add(concreteComponentDescriptorProvider<ParagraphComponentDescriptor>());
|
|
43
|
+
registry->add(concreteComponentDescriptorProvider<TextComponentDescriptor>());
|
|
44
|
+
registry->add(concreteComponentDescriptorProvider<RawTextComponentDescriptor>());
|
|
45
|
+
|
|
46
|
+
// Image's descriptor pulls an ImageManager out of the ContextContainer,
|
|
47
|
+
// creating one if absent. React Native's cxx ImageManager is a stub, so
|
|
48
|
+
// it produces no pixels; the mounting manager loads them from the props,
|
|
49
|
+
// which is what Android does too.
|
|
50
|
+
registry->add(concreteComponentDescriptorProvider<ImageComponentDescriptor>());
|
|
51
|
+
|
|
52
|
+
// ScrollView's content child arrives as "ScrollContentView", which
|
|
53
|
+
// React Native's own registry rewrites to "View" before it reaches here,
|
|
54
|
+
// so it needs no entry of its own. What the descriptor is really for is
|
|
55
|
+
// ScrollViewState: without it the substituted UnimplementedNativeView has
|
|
56
|
+
// nowhere to keep a content offset, so a ScrollView renders and never
|
|
57
|
+
// scrolls -- silently, which is why the absence used to be worth naming.
|
|
58
|
+
registry->add(concreteComponentDescriptorProvider<ScrollViewComponentDescriptor>());
|
|
59
|
+
|
|
60
|
+
// React Native's *iOS* TextInput, whose shadow node, props, state and
|
|
61
|
+
// event emitter are pure C++ and measure through a TextLayoutManager --
|
|
62
|
+
// the DirectWrite one, here. Android's variant includes fbjni and calls
|
|
63
|
+
// into a Java FabricUIManager, so it cannot be used outside an Android
|
|
64
|
+
// build. Both other desktops made the same choice; see
|
|
65
|
+
// docs/DECISIONS.md. Its component name is "TextInput", which is what
|
|
66
|
+
// React Native's own componentNameByReactViewName maps
|
|
67
|
+
// RCTSinglelineTextInputView to, so nothing here renames anything.
|
|
68
|
+
registry->add(concreteComponentDescriptorProvider<TextInputComponentDescriptor>());
|
|
69
|
+
|
|
70
|
+
// Deliberately absent, and the absence is a decision rather than an
|
|
71
|
+
// oversight: ExpoImage's seam exists in core/ExpoImageComponent.h and its
|
|
72
|
+
// props class is portable, but nothing here mounts one yet.
|
|
73
|
+
//
|
|
74
|
+
// See docs/BACKLOG.md.
|
|
75
|
+
|
|
76
|
+
// --- The controls -----------------------------------------------------
|
|
77
|
+
//
|
|
78
|
+
// Everything above is a box with something drawn in it. These are not:
|
|
79
|
+
// each one is a toolkit control, or a window, and each one needed the
|
|
80
|
+
// view layer to grow a peer before registering it here would have meant
|
|
81
|
+
// anything. Registering a descriptor with nothing to mount it does not
|
|
82
|
+
// error -- Fabric builds the shadow nodes, lays them out, and the app
|
|
83
|
+
// renders blank rectangles -- so this list and the mounting manager's
|
|
84
|
+
// `hasComponent` are two statements of the same fact and have to agree.
|
|
85
|
+
|
|
86
|
+
// <ActivityIndicator>. React Native's JavaScript writes the 20pt or 36pt
|
|
87
|
+
// box into the view's style, so the shadow node needs no measurement of
|
|
88
|
+
// its own; what the host draws inside it is a spinner.
|
|
89
|
+
registry->add(concreteComponentDescriptorProvider<ActivityIndicatorViewComponentDescriptor>());
|
|
90
|
+
// <Switch>. The descriptor is React Native's iOS one -- portable C++
|
|
91
|
+
// apart from a `measureContent` that instantiates a UISwitch, which
|
|
92
|
+
// core/DesktopControls.cpp replaces with a constant so a switch is the
|
|
93
|
+
// same size on all three desktops.
|
|
94
|
+
registry->add(concreteComponentDescriptorProvider<SwitchComponentDescriptor>());
|
|
95
|
+
// <Modal>. Not a second window: the shadow node is a root-kind node sized
|
|
96
|
+
// from its state and positioned absolutely, so a modal is an overlay
|
|
97
|
+
// filling the surface -- which is also what it is on the web. The host
|
|
98
|
+
// commits the window's size into that state; see
|
|
99
|
+
// core/DesktopControls.h's modalScreenSizeNeedsUpdate for why it must.
|
|
100
|
+
registry->add(concreteComponentDescriptorProvider<ModalHostViewComponentDescriptor>());
|
|
101
|
+
// <RefreshControl>. The spinner a scroll view shows while it refreshes.
|
|
102
|
+
// The gesture that starts it is in core/PullToRefresh.h, because a
|
|
103
|
+
// desktop has no rubber band to release.
|
|
104
|
+
registry->add(concreteComponentDescriptorProvider<PullToRefreshViewComponentDescriptor>());
|
|
105
|
+
// What React Native substitutes for a component no platform registered.
|
|
106
|
+
// Registering it is what turns "nothing happens" into a mounted view the
|
|
107
|
+
// tree dump can show, and it is what the comments above mean when they
|
|
108
|
+
// say the substitution is silent.
|
|
109
|
+
registry->add(concreteComponentDescriptorProvider<UnimplementedNativeViewComponentDescriptor>());
|
|
110
|
+
// React DevTools' element highlighter. It draws nothing here yet -- the
|
|
111
|
+
// highlight arrives as a command rather than as props -- but a DevTools
|
|
112
|
+
// session mounts one, and an unregistered component in the tree is a
|
|
113
|
+
// blank rectangle in the middle of the app.
|
|
114
|
+
registry->add(concreteComponentDescriptorProvider<DebuggingOverlayComponentDescriptor>());
|
|
115
|
+
|
|
116
|
+
return registry;
|
|
117
|
+
}();
|
|
118
|
+
|
|
119
|
+
return providerRegistry->createComponentDescriptorRegistry(
|
|
120
|
+
{eventDispatcher, contextContainer, nullptr});
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
} // namespace facebook::react
|