basalt-win32 0.0.0-rc5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/LICENSE +21 -0
  2. package/cli/runWindows.js +35 -0
  3. package/cli/shared.js +75 -0
  4. package/native/CMakeLists.txt +398 -0
  5. package/native/tests/test_win32_accessibility.cpp +271 -0
  6. package/native/tests/test_win32_clipboard.cpp +121 -0
  7. package/native/tests/test_win32_focus.cpp +195 -0
  8. package/native/tests/test_win32_hittest.cpp +410 -0
  9. package/native/tests/test_win32_image.cpp +239 -0
  10. package/native/tests/test_win32_imageloader.cpp +208 -0
  11. package/native/tests/test_win32_input.cpp +361 -0
  12. package/native/tests/test_win32_key_events.cpp +84 -0
  13. package/native/tests/test_win32_mounting.cpp +410 -0
  14. package/native/tests/test_win32_paint.cpp +505 -0
  15. package/native/tests/test_win32_scrollview.cpp +385 -0
  16. package/native/tests/test_win32_text.cpp +258 -0
  17. package/native/tests/test_win32_textinput.cpp +609 -0
  18. package/native/tests/test_win32_titlebar.cpp +111 -0
  19. package/native/tests/test_win32_titlebar_actions.cpp +62 -0
  20. package/native/tests/test_win32_view.cpp +222 -0
  21. package/native/win32/ColorSchemeWin32.cpp +56 -0
  22. package/native/win32/ComponentRegistryWin32.cpp +124 -0
  23. package/native/win32/DirectWriteLayout.cpp +116 -0
  24. package/native/win32/DirectWriteLayout.h +36 -0
  25. package/native/win32/DirectWriteLayoutManager.cpp +124 -0
  26. package/native/win32/FontRegistryDirectWrite.cpp +117 -0
  27. package/native/win32/PlatformServicesWin32.cpp +643 -0
  28. package/native/win32/RnWin32Accessible.cpp +258 -0
  29. package/native/win32/RnWin32Accessible.h +91 -0
  30. package/native/win32/RnWin32Image.cpp +265 -0
  31. package/native/win32/RnWin32Image.h +93 -0
  32. package/native/win32/RnWin32TextLayout.cpp +310 -0
  33. package/native/win32/RnWin32TextLayout.h +160 -0
  34. package/native/win32/RnWin32View.cpp +1281 -0
  35. package/native/win32/RnWin32View.h +506 -0
  36. package/native/win32/Win32AnimationChoreographer.cpp +76 -0
  37. package/native/win32/Win32AnimationChoreographer.h +61 -0
  38. package/native/win32/Win32Clip.h +173 -0
  39. package/native/win32/Win32DropTarget.cpp +567 -0
  40. package/native/win32/Win32DropTarget.h +42 -0
  41. package/native/win32/Win32Focus.cpp +262 -0
  42. package/native/win32/Win32Focus.h +109 -0
  43. package/native/win32/Win32ImageLoader.cpp +167 -0
  44. package/native/win32/Win32ImageLoader.h +104 -0
  45. package/native/win32/Win32KeyEvents.cpp +126 -0
  46. package/native/win32/Win32KeyEvents.h +20 -0
  47. package/native/win32/Win32MenuBar.cpp +352 -0
  48. package/native/win32/Win32MenuBar.h +16 -0
  49. package/native/win32/Win32MountingManager.cpp +727 -0
  50. package/native/win32/Win32MountingManager.h +203 -0
  51. package/native/win32/Win32Packaging.cpp +161 -0
  52. package/native/win32/Win32Packaging.h +56 -0
  53. package/native/win32/Win32RunLoopObserver.cpp +48 -0
  54. package/native/win32/Win32RunLoopObserver.h +42 -0
  55. package/native/win32/Win32ScrollView.cpp +515 -0
  56. package/native/win32/Win32ScrollView.h +208 -0
  57. package/native/win32/Win32SkiaContext.cpp +221 -0
  58. package/native/win32/Win32SkiaContext.h +53 -0
  59. package/native/win32/Win32SkiaModule.cpp +80 -0
  60. package/native/win32/Win32SkiaModule.h +50 -0
  61. package/native/win32/Win32SkiaPeer.cpp +358 -0
  62. package/native/win32/Win32SkiaPeer.h +66 -0
  63. package/native/win32/Win32SkiaUnicode.cpp +42 -0
  64. package/native/win32/Win32Snapshot.cpp +201 -0
  65. package/native/win32/Win32Snapshot.h +66 -0
  66. package/native/win32/Win32Strings.h +54 -0
  67. package/native/win32/Win32TextInput.cpp +811 -0
  68. package/native/win32/Win32TextInput.h +232 -0
  69. package/native/win32/Win32TitleBar.cpp +562 -0
  70. package/native/win32/Win32TitleBar.h +146 -0
  71. package/native/win32/Win32TitleBarLayout.cpp +46 -0
  72. package/native/win32/Win32TitleBarLayout.h +58 -0
  73. package/native/win32/Win32TouchDispatcher.cpp +443 -0
  74. package/native/win32/Win32TouchDispatcher.h +153 -0
  75. package/native/win32/Win32UiThread.h +27 -0
  76. package/native/win32/Win32WindowControl.cpp +329 -0
  77. package/native/win32/Win32WindowModule.cpp +209 -0
  78. package/native/win32/Win32WindowModule.h +72 -0
  79. package/native/win32/demo_layout_win32.cpp +232 -0
  80. package/native/win32/main_win32.cpp +2481 -0
  81. package/native/win32/mount_harness_win32.cpp +316 -0
  82. package/package.json +26 -0
  83. package/react-native.config.js +22 -0
@@ -0,0 +1,209 @@
1
+ #include "Win32WindowModule.h"
2
+
3
+ #include "WindowControl.h"
4
+ #include "WindowMethods.h"
5
+ #include "PlatformServices.h"
6
+ #include "Win32TitleBar.h"
7
+
8
+ #include <windows.h>
9
+
10
+ #include <cstdint>
11
+ #include <optional>
12
+ #include <string>
13
+ #include <utility>
14
+ #include <vector>
15
+
16
+ namespace basalt {
17
+
18
+ namespace {
19
+
20
+ using facebook::jsi::Object;
21
+ using facebook::jsi::Runtime;
22
+ using facebook::jsi::String;
23
+ using facebook::jsi::Value;
24
+ using facebook::react::TurboModule;
25
+
26
+ constexpr const char *kMetricsChangedEvent = "basaltTitleBarMetricsChanged";
27
+
28
+ // processColor's 0xAARRGGBB number, as the COLORREF DWM takes, or nullopt for
29
+ // anything that is not a number -- which is how JavaScript asks for the
30
+ // system's colour back. The alpha is dropped: a caption is opaque.
31
+ std::optional<COLORREF> colorArgument(const Value *args, size_t count, size_t index) {
32
+ if (index >= count || !args[index].isNumber()) {
33
+ return std::nullopt;
34
+ }
35
+ const auto argb = static_cast<uint32_t>(static_cast<int64_t>(args[index].asNumber()));
36
+ return RGB((argb >> 16) & 0xFF, (argb >> 8) & 0xFF, argb & 0xFF);
37
+ }
38
+
39
+ Value metricsValue(Runtime &runtime, const TitleBarMetrics &metrics) {
40
+ Object object(runtime);
41
+ object.setProperty(runtime, "height", static_cast<double>(metrics.height));
42
+ object.setProperty(runtime, "buttonsWidth", static_cast<double>(metrics.buttonsWidth));
43
+ object.setProperty(runtime,
44
+ "style",
45
+ String::createFromAscii(
46
+ runtime, metrics.style == TitleBarStyle::Hidden ? "hidden" : "native"));
47
+ return Value(runtime, object);
48
+ }
49
+
50
+ } // namespace
51
+
52
+ Win32WindowModule::Win32WindowModule(std::shared_ptr<facebook::react::CallInvoker> jsInvoker)
53
+ : TurboModule(kModuleName, std::move(jsInvoker)) {
54
+ methodMap_["setTitle"] = MethodMetadata{1, setTitle};
55
+ methodMap_["setTitleBarColors"] = MethodMetadata{3, setTitleBarColors};
56
+ methodMap_["setTitleBarStyle"] = MethodMetadata{1, setTitleBarStyle};
57
+ methodMap_["getTitleBarMetrics"] = MethodMetadata{0, getTitleBarMetrics};
58
+ // The caption's actions, under the names and in the order GtkWindowModule
59
+ // and AppKitWindowModule already use. src/useWindow.ts calls each one
60
+ // optionally -- NativeWindow?.minimize?.() -- so a host that leaves them out
61
+ // does not fail, it silently does nothing, which is how these were missing
62
+ // here while working on the other two desktops.
63
+ methodMap_["minimize"] = MethodMetadata{0, minimize};
64
+ methodMap_["toggleMaximize"] = MethodMetadata{0, toggleMaximize};
65
+ methodMap_["close"] = MethodMetadata{0, closeWindow};
66
+ methodMap_["startWindowDrag"] = MethodMetadata{0, startWindowDrag};
67
+ // A window's geometry. The bodies are in core/WindowMethods.h, written once:
68
+ // nothing about reading two numbers or shaping the answer differs per
69
+ // desktop, and three copies would be three chances to drift.
70
+ methodMap_["setSize"] = MethodMetadata{2, windowSetSize};
71
+ methodMap_["setPosition"] = MethodMetadata{2, windowSetPosition};
72
+ methodMap_["center"] = MethodMetadata{0, windowCenter};
73
+ methodMap_["setFullScreen"] = MethodMetadata{1, windowSetFullScreen};
74
+ // How big it may be, whether it may be resized at all, and whether it floats.
75
+ // Not every desktop does every one of these; `getCapabilities` is how an app
76
+ // finds out rather than guessing. See core/WindowControl.h.
77
+ methodMap_["setMinimumSize"] = MethodMetadata{2, windowSetMinimumSize};
78
+ methodMap_["setMaximumSize"] = MethodMetadata{2, windowSetMaximumSize};
79
+ methodMap_["setResizable"] = MethodMetadata{1, windowSetResizable};
80
+ methodMap_["setAlwaysOnTop"] = MethodMetadata{1, windowSetAlwaysOnTop};
81
+ methodMap_["getCapabilities"] = MethodMetadata{0, windowGetCapabilities};
82
+ methodMap_["getBounds"] = MethodMetadata{0, windowGetBounds};
83
+ // What a NativeEventEmitter over this module calls; the events go out as
84
+ // device events either way.
85
+ methodMap_["addListener"] = MethodMetadata{1, noop};
86
+ methodMap_["removeListeners"] = MethodMetadata{1, noop};
87
+
88
+ // Cleared in the destructor, the same arrangement AppearanceModule has with
89
+ // its colour scheme observer.
90
+ titleBar().setMetricsListener([this](const TitleBarMetrics &metrics) {
91
+ emitDeviceEvent(kMetricsChangedEvent, [metrics](Runtime &runtime, std::vector<Value> &args) {
92
+ args.emplace_back(metricsValue(runtime, metrics));
93
+ });
94
+ });
95
+
96
+ // The window's geometry, the same way the title bar's metrics go out: a
97
+ // device event, so `useWindow()` can hand an app live bounds rather than a
98
+ // snapshot it has to remember to refresh.
99
+ setWindowBoundsListener([this](const WindowBounds &bounds) {
100
+ emitDeviceEvent(kWindowBoundsEvent, [bounds](Runtime &runtime, std::vector<Value> &args) {
101
+ Object object(runtime);
102
+ object.setProperty(runtime, "x", bounds.x);
103
+ object.setProperty(runtime, "y", bounds.y);
104
+ object.setProperty(runtime, "width", bounds.width);
105
+ object.setProperty(runtime, "height", bounds.height);
106
+ object.setProperty(runtime, "fullScreen", bounds.fullScreen);
107
+ object.setProperty(runtime, "maximized", bounds.maximized);
108
+ args.emplace_back(Value(runtime, object));
109
+ });
110
+ });
111
+
112
+ // And ask for the bounds once, now that something is listening. The host
113
+ // primes the cache when its window appears, but this module is built lazily
114
+ // -- the first time JavaScript asks for it -- so an app that mounts before
115
+ // then would read a window of no size and never be told otherwise.
116
+ postToUiThread([] { notifyWindowBoundsChanged(); });
117
+ }
118
+
119
+ Win32WindowModule::~Win32WindowModule() {
120
+ titleBar().setMetricsListener(nullptr);
121
+ setWindowBoundsListener(nullptr);
122
+ }
123
+
124
+ Value Win32WindowModule::setTitle(Runtime &runtime,
125
+ TurboModule & /*module*/,
126
+ const Value *args,
127
+ size_t count) {
128
+ std::optional<std::string> title;
129
+ if (count > 0 && args[0].isString()) {
130
+ title = args[0].getString(runtime).utf8(runtime);
131
+ }
132
+ postToUiThread([title = std::move(title)]() mutable { titleBar().setTitle(std::move(title)); });
133
+ return Value::undefined();
134
+ }
135
+
136
+ Value Win32WindowModule::setTitleBarColors(Runtime & /*runtime*/,
137
+ TurboModule & /*module*/,
138
+ const Value *args,
139
+ size_t count) {
140
+ const auto caption = colorArgument(args, count, 0);
141
+ const auto text = colorArgument(args, count, 1);
142
+ const auto border = colorArgument(args, count, 2);
143
+ postToUiThread([caption, text, border] { titleBar().setColors(caption, text, border); });
144
+ return Value::undefined();
145
+ }
146
+
147
+ Value Win32WindowModule::setTitleBarStyle(Runtime &runtime,
148
+ TurboModule & /*module*/,
149
+ const Value *args,
150
+ size_t count) {
151
+ const bool hidden =
152
+ count > 0 && args[0].isString() && args[0].getString(runtime).utf8(runtime) == "hidden";
153
+ postToUiThread([hidden] {
154
+ titleBar().setStyle(hidden ? TitleBarStyle::Hidden : TitleBarStyle::Native);
155
+ });
156
+ return Value::undefined();
157
+ }
158
+
159
+ Value Win32WindowModule::getTitleBarMetrics(Runtime &runtime,
160
+ TurboModule & /*module*/,
161
+ const Value * /*args*/,
162
+ size_t /*count*/) {
163
+ return metricsValue(runtime, titleBar().metrics());
164
+ }
165
+
166
+ // The four below are GtkWindowModule's bodies with the toolkit swapped: the
167
+ // work is one call on the UI thread, and the shape is identical on purpose, so
168
+ // that a reader comparing the three hosts sees the same thing three times
169
+ // rather than three arrangements of it.
170
+ Value Win32WindowModule::minimize(Runtime & /*runtime*/,
171
+ TurboModule & /*module*/,
172
+ const Value * /*args*/,
173
+ size_t /*count*/) {
174
+ postToUiThread([] { titleBar().minimize(); });
175
+ return Value::undefined();
176
+ }
177
+
178
+ Value Win32WindowModule::toggleMaximize(Runtime & /*runtime*/,
179
+ TurboModule & /*module*/,
180
+ const Value * /*args*/,
181
+ size_t /*count*/) {
182
+ postToUiThread([] { titleBar().toggleMaximize(); });
183
+ return Value::undefined();
184
+ }
185
+
186
+ Value Win32WindowModule::closeWindow(Runtime & /*runtime*/,
187
+ TurboModule & /*module*/,
188
+ const Value * /*args*/,
189
+ size_t /*count*/) {
190
+ postToUiThread([] { titleBar().close(); });
191
+ return Value::undefined();
192
+ }
193
+
194
+ Value Win32WindowModule::startWindowDrag(Runtime & /*runtime*/,
195
+ TurboModule & /*module*/,
196
+ const Value * /*args*/,
197
+ size_t /*count*/) {
198
+ postToUiThread([] { titleBar().startDrag(); });
199
+ return Value::undefined();
200
+ }
201
+
202
+ Value Win32WindowModule::noop(Runtime & /*runtime*/,
203
+ TurboModule & /*module*/,
204
+ const Value * /*args*/,
205
+ size_t /*count*/) {
206
+ return Value::undefined();
207
+ }
208
+
209
+ } // namespace basalt
@@ -0,0 +1,72 @@
1
+ // BasaltWindow: the window's title bar, reached from JavaScript.
2
+ //
3
+ // basalt-core's src/TitleBar.js is the API over this -- useTitleBar,
4
+ // <TitleBar>, useTitleBarMetrics -- and Win32TitleBar is what it drives. A
5
+ // module with no React Native spec, written the way the gesture handler one
6
+ // is, because it is this platform's own and there is no spec for it to follow.
7
+ //
8
+ // Every method is called on the JavaScript thread and posts its request to the
9
+ // UI thread, where the window lives. getTitleBarMetrics answers from a copy the
10
+ // title bar keeps under a lock, and "basaltTitleBarMetricsChanged" is emitted
11
+ // whenever that copy changes.
12
+
13
+ #pragma once
14
+
15
+ #include <ReactCommon/TurboModule.h>
16
+
17
+ #include <memory>
18
+
19
+ namespace basalt {
20
+
21
+ class Win32WindowModule : public facebook::react::TurboModule {
22
+ public:
23
+ static constexpr const char *kModuleName = "BasaltWindow";
24
+
25
+ explicit Win32WindowModule(std::shared_ptr<facebook::react::CallInvoker> jsInvoker);
26
+ ~Win32WindowModule() override;
27
+
28
+ private:
29
+ static facebook::jsi::Value setTitle(facebook::jsi::Runtime &runtime,
30
+ facebook::react::TurboModule &module,
31
+ const facebook::jsi::Value *args,
32
+ size_t count);
33
+ static facebook::jsi::Value setTitleBarColors(facebook::jsi::Runtime &runtime,
34
+ facebook::react::TurboModule &module,
35
+ const facebook::jsi::Value *args,
36
+ size_t count);
37
+ static facebook::jsi::Value setTitleBarStyle(facebook::jsi::Runtime &runtime,
38
+ facebook::react::TurboModule &module,
39
+ const facebook::jsi::Value *args,
40
+ size_t count);
41
+ static facebook::jsi::Value getTitleBarMetrics(facebook::jsi::Runtime &runtime,
42
+ facebook::react::TurboModule &module,
43
+ const facebook::jsi::Value *args,
44
+ size_t count);
45
+ // The caption's four actions. GtkWindowModule and AppKitWindowModule
46
+ // register the same four under the same names; this host registered none of
47
+ // them, so useWindow().minimize() and .close() were silent no-ops here while
48
+ // working on the other two desktops -- and useCloseRequest could refuse a
49
+ // close and then not be able to honour agreeing to it.
50
+ static facebook::jsi::Value minimize(facebook::jsi::Runtime &runtime,
51
+ facebook::react::TurboModule &module,
52
+ const facebook::jsi::Value *args,
53
+ size_t count);
54
+ static facebook::jsi::Value toggleMaximize(facebook::jsi::Runtime &runtime,
55
+ facebook::react::TurboModule &module,
56
+ const facebook::jsi::Value *args,
57
+ size_t count);
58
+ static facebook::jsi::Value closeWindow(facebook::jsi::Runtime &runtime,
59
+ facebook::react::TurboModule &module,
60
+ const facebook::jsi::Value *args,
61
+ size_t count);
62
+ static facebook::jsi::Value startWindowDrag(facebook::jsi::Runtime &runtime,
63
+ facebook::react::TurboModule &module,
64
+ const facebook::jsi::Value *args,
65
+ size_t count);
66
+ static facebook::jsi::Value noop(facebook::jsi::Runtime &runtime,
67
+ facebook::react::TurboModule &module,
68
+ const facebook::jsi::Value *args,
69
+ size_t count);
70
+ };
71
+
72
+ } // namespace basalt
@@ -0,0 +1,232 @@
1
+ // The Windows view layer, driven by hand-written frames.
2
+ //
3
+ // The same standing-in-for-Fabric exercise as gtk/demo_layout_gtk.cpp and
4
+ // appkit/demo_layout_appkit.mm, with the same boxes at the same coordinates, so
5
+ // the three platforms can be compared directly rather than by eye. Builds and
6
+ // runs without React Native's C++ core, which keeps the paint and placement
7
+ // path verifiable on its own.
8
+ //
9
+ // Three modes. BASALT_DUMP_TREE prints the tree and exits. BASALT_SNAPSHOT=<png>
10
+ // renders the hierarchy offscreen and writes it, which is the mode that proves
11
+ // anything: the tree dump would look identical whether the transform
12
+ // composition works or not, because it prints the frames that were set rather
13
+ // than where they landed. Neither needs a window. Without either, it opens one.
14
+ //
15
+ // A console subsystem rather than a GUI one, deliberately: BASALT_DUMP_TREE has
16
+ // to reach a pipe, and `scripts/compare_hosts.sh` is what reads it.
17
+
18
+ #include "RnWin32View.h"
19
+ #include "Win32Snapshot.h"
20
+
21
+ #include <windows.h>
22
+
23
+ #include <d2d1.h>
24
+ #include <wrl/client.h>
25
+
26
+ #include <cstdio>
27
+ #include <cstdlib>
28
+ #include <memory>
29
+ #include <string>
30
+ #include <vector>
31
+
32
+ using Microsoft::WRL::ComPtr;
33
+ using basalt::win32::RnWin32View;
34
+
35
+ namespace {
36
+
37
+ // The demo owns its views. In the real thing the mounting registry does; a view
38
+ // never owns its children, which is what makes Fabric's detach-without-destroy
39
+ // structural rather than a reference count to get right.
40
+ std::vector<std::unique_ptr<RnWin32View>> g_views;
41
+
42
+ RnWin32View *makeBox(int32_t tag,
43
+ float x,
44
+ float y,
45
+ float w,
46
+ float h,
47
+ float r,
48
+ float g,
49
+ float b,
50
+ float a) {
51
+ auto view = std::make_unique<RnWin32View>(tag);
52
+ view->setFrame(x, y, w, h);
53
+ view->setBackgroundColor(r, g, b, a, true);
54
+ RnWin32View *raw = view.get();
55
+ g_views.push_back(std::move(view));
56
+ return raw;
57
+ }
58
+
59
+ RnWin32View *buildTree() {
60
+ RnWin32View *root = makeBox(1, 0, 0, 640, 420, 0.12f, 0.13f, 0.16f, 1.0f);
61
+
62
+ // A row of children, absolutely placed the way Yoga would have resolved them.
63
+ RnWin32View *a = makeBox(2, 32, 32, 240, 160, 0.30f, 0.55f, 0.95f, 1.0f);
64
+ RnWin32View *b = makeBox(3, 296, 32, 240, 160, 0.95f, 0.45f, 0.35f, 1.0f);
65
+ RnWin32View *c = makeBox(4, 32, 224, 504, 140, 0.35f, 0.80f, 0.55f, 1.0f);
66
+
67
+ root->insertChild(a, 0);
68
+ root->insertChild(b, 1);
69
+ root->insertChild(c, 2);
70
+
71
+ // Nested child, to prove coordinates are parent-relative. Win32 and React
72
+ // Native agree on a top-left origin, so unlike the AppKit side there is no
73
+ // flip for this to catch -- what it catches here is a transform composed in
74
+ // the wrong order, which puts it 24 units from the wrong edge just as surely.
75
+ RnWin32View *nested = makeBox(5, 24, 24, 120, 90, 1.0f, 1.0f, 1.0f, 0.85f);
76
+ a->insertChild(nested, 0);
77
+
78
+ // Half-opacity child, to prove the opacity layer. Distinct from the alpha
79
+ // above: that one is a translucent colour, this one is a translucent view.
80
+ RnWin32View *faded = makeBox(6, 24, 24, 160, 90, 0.1f, 0.1f, 0.1f, 1.0f);
81
+ faded->setOpacity(0.4f);
82
+ c->insertChild(faded, 0);
83
+
84
+ return root;
85
+ }
86
+
87
+ // --- The window ------------------------------------------------------------
88
+ //
89
+ // One HWND for the whole tree. See RnWin32View.h for why a view is not a
90
+ // window; this is the other half of that decision, and it is what the host will
91
+ // grow into.
92
+
93
+ struct DemoWindow {
94
+ RnWin32View *root = nullptr;
95
+ ComPtr<ID2D1Factory> factory;
96
+ ComPtr<ID2D1HwndRenderTarget> target;
97
+ };
98
+
99
+ bool ensureTarget(DemoWindow *demo, HWND hwnd) {
100
+ if (demo->target) {
101
+ return true;
102
+ }
103
+ RECT client{};
104
+ GetClientRect(hwnd, &client);
105
+ const D2D1_SIZE_U size = D2D1::SizeU(static_cast<UINT32>(client.right - client.left),
106
+ static_cast<UINT32>(client.bottom - client.top));
107
+ if (size.width == 0 || size.height == 0) {
108
+ return false;
109
+ }
110
+ return SUCCEEDED(demo->factory->CreateHwndRenderTarget(
111
+ D2D1::RenderTargetProperties(), D2D1::HwndRenderTargetProperties(hwnd, size), &demo->target));
112
+ }
113
+
114
+ LRESULT CALLBACK windowProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
115
+ auto *demo = reinterpret_cast<DemoWindow *>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
116
+
117
+ switch (message) {
118
+ case WM_CREATE: {
119
+ auto *create = reinterpret_cast<CREATESTRUCT *>(lparam);
120
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(create->lpCreateParams));
121
+ return 0;
122
+ }
123
+
124
+ case WM_SIZE:
125
+ if (demo != nullptr && demo->target) {
126
+ demo->target->Resize(D2D1::SizeU(LOWORD(lparam), HIWORD(lparam)));
127
+ }
128
+ return 0;
129
+
130
+ case WM_PAINT: {
131
+ PAINTSTRUCT paint{};
132
+ BeginPaint(hwnd, &paint);
133
+ if (demo != nullptr && ensureTarget(demo, hwnd)) {
134
+ demo->target->BeginDraw();
135
+ demo->target->Clear(D2D1::ColorF(D2D1::ColorF::Black));
136
+ demo->target->SetTransform(D2D1::Matrix3x2F::Identity());
137
+ demo->root->paint(demo->target.Get());
138
+ // A lost device is reported here and nowhere else. Dropping the target
139
+ // is the whole recovery: the next WM_PAINT rebuilds it.
140
+ if (demo->target->EndDraw() == D2DERR_RECREATE_TARGET) {
141
+ demo->target.Reset();
142
+ }
143
+ }
144
+ EndPaint(hwnd, &paint);
145
+ return 0;
146
+ }
147
+
148
+ case WM_DESTROY:
149
+ PostQuitMessage(0);
150
+ return 0;
151
+
152
+ default:
153
+ break;
154
+ }
155
+ return DefWindowProc(hwnd, message, wparam, lparam);
156
+ }
157
+
158
+ int runWindow(RnWin32View *root) {
159
+ DemoWindow demo;
160
+ demo.root = root;
161
+ if (FAILED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, demo.factory.GetAddressOf()))) {
162
+ std::fputs("could not create a Direct2D factory\n", stderr);
163
+ return 1;
164
+ }
165
+
166
+ const HINSTANCE instance = GetModuleHandle(nullptr);
167
+ WNDCLASSEX windowClass{};
168
+ windowClass.cbSize = sizeof(windowClass);
169
+ windowClass.style = CS_HREDRAW | CS_VREDRAW;
170
+ windowClass.lpfnWndProc = windowProc;
171
+ windowClass.hInstance = instance;
172
+ windowClass.hCursor = LoadCursor(nullptr, IDC_ARROW);
173
+ windowClass.lpszClassName = L"BasaltDemoLayout";
174
+ if (RegisterClassEx(&windowClass) == 0) {
175
+ std::fputs("could not register the window class\n", stderr);
176
+ return 1;
177
+ }
178
+
179
+ // The client area is the surface, so the window is grown by whatever the
180
+ // frame costs. Getting this wrong scales the whole tree by a few per cent,
181
+ // which is exactly the kind of difference a cross-host comparison is for.
182
+ RECT wanted{0, 0, static_cast<LONG>(root->frame().width),
183
+ static_cast<LONG>(root->frame().height)};
184
+ AdjustWindowRect(&wanted, WS_OVERLAPPEDWINDOW, FALSE);
185
+
186
+ const HWND hwnd = CreateWindowEx(0,
187
+ windowClass.lpszClassName,
188
+ L"basalt-core \x2014 Windows layout skeleton",
189
+ WS_OVERLAPPEDWINDOW,
190
+ CW_USEDEFAULT,
191
+ CW_USEDEFAULT,
192
+ wanted.right - wanted.left,
193
+ wanted.bottom - wanted.top,
194
+ nullptr,
195
+ nullptr,
196
+ instance,
197
+ &demo);
198
+ if (hwnd == nullptr) {
199
+ std::fputs("could not create the window\n", stderr);
200
+ return 1;
201
+ }
202
+
203
+ ShowWindow(hwnd, SW_SHOWNORMAL);
204
+ UpdateWindow(hwnd);
205
+
206
+ MSG message{};
207
+ while (GetMessage(&message, nullptr, 0, 0) > 0) {
208
+ TranslateMessage(&message);
209
+ DispatchMessage(&message);
210
+ }
211
+ return static_cast<int>(message.wParam);
212
+ }
213
+
214
+ } // namespace
215
+
216
+ int main(int argc, char **argv) {
217
+ (void)argc;
218
+ (void)argv;
219
+
220
+ RnWin32View *root = buildTree();
221
+
222
+ if (std::getenv("BASALT_DUMP_TREE") != nullptr) {
223
+ std::fputs(root->describeTree().c_str(), stdout);
224
+ return 0;
225
+ }
226
+
227
+ if (const char *snapshotPath = std::getenv("BASALT_SNAPSHOT")) {
228
+ return basalt::win32::writeSnapshot(*root, snapshotPath) ? 0 : 1;
229
+ }
230
+
231
+ return runWindow(root);
232
+ }