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,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
|
+
}
|