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,567 @@
|
|
|
1
|
+
#include "Win32DropTarget.h"
|
|
2
|
+
|
|
3
|
+
#include "DragAndDrop.h"
|
|
4
|
+
|
|
5
|
+
#include <objidl.h>
|
|
6
|
+
#include <ole2.h>
|
|
7
|
+
// HDROP and DragQueryFileW, which are shellapi's and not shlobj's -- shlobj
|
|
8
|
+
// includes it on some SDK configurations and not this one, which is the kind
|
|
9
|
+
// of difference only a real Windows compile finds.
|
|
10
|
+
#include <shellapi.h>
|
|
11
|
+
#include <shlobj.h>
|
|
12
|
+
|
|
13
|
+
#include <string>
|
|
14
|
+
#include <vector>
|
|
15
|
+
|
|
16
|
+
namespace basalt {
|
|
17
|
+
namespace {
|
|
18
|
+
|
|
19
|
+
// The path from the root down to `target`, root first, or empty if the target
|
|
20
|
+
// is not in this tree.
|
|
21
|
+
//
|
|
22
|
+
// This exists because RnWin32View has no parent pointer and the rule needs
|
|
23
|
+
// ancestors. The alternative -- a second walk down the tree applying the hit
|
|
24
|
+
// rules again -- would be a copy of `hitTest`'s handling of transforms,
|
|
25
|
+
// clipping, hidden views and pointer events, which is exactly the kind of
|
|
26
|
+
// duplication that drifts. Finding the path costs one traversal of a tree
|
|
27
|
+
// that is already small enough to hit-test on every mouse move.
|
|
28
|
+
bool pathTo(win32::RnWin32View *view,
|
|
29
|
+
win32::RnWin32View *target,
|
|
30
|
+
std::vector<win32::RnWin32View *> &path) {
|
|
31
|
+
if (view == nullptr) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
path.push_back(view);
|
|
35
|
+
if (view == target) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
for (win32::RnWin32View *child : view->children()) {
|
|
39
|
+
if (pathTo(child, target, path)) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
path.pop_back();
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
std::wstring widen(const std::string &text) {
|
|
48
|
+
if (text.empty()) {
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
const int needed =
|
|
52
|
+
MultiByteToWideChar(CP_UTF8, 0, text.c_str(), static_cast<int>(text.size()), nullptr, 0);
|
|
53
|
+
std::wstring wide(static_cast<size_t>(needed), L'\0');
|
|
54
|
+
MultiByteToWideChar(
|
|
55
|
+
CP_UTF8, 0, text.c_str(), static_cast<int>(text.size()), wide.data(), needed);
|
|
56
|
+
return wide;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
std::string narrow(const wchar_t *text) {
|
|
60
|
+
if (text == nullptr) {
|
|
61
|
+
return {};
|
|
62
|
+
}
|
|
63
|
+
const int needed = WideCharToMultiByte(CP_UTF8, 0, text, -1, nullptr, 0, nullptr, nullptr);
|
|
64
|
+
if (needed <= 1) {
|
|
65
|
+
return {};
|
|
66
|
+
}
|
|
67
|
+
std::string out(static_cast<size_t>(needed - 1), '\0');
|
|
68
|
+
WideCharToMultiByte(CP_UTF8, 0, text, -1, out.data(), needed, nullptr, nullptr);
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// What the data object is offering, before the drop.
|
|
73
|
+
std::uint16_t offeredBy(IDataObject *data) {
|
|
74
|
+
std::uint16_t offered = DropAcceptsNone;
|
|
75
|
+
if (data == nullptr) {
|
|
76
|
+
return offered;
|
|
77
|
+
}
|
|
78
|
+
FORMATETC files{CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
|
79
|
+
if (data->QueryGetData(&files) == S_OK) {
|
|
80
|
+
offered |= DropAcceptsFiles;
|
|
81
|
+
}
|
|
82
|
+
FORMATETC text{CF_UNICODETEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
|
83
|
+
if (data->QueryGetData(&text) == S_OK) {
|
|
84
|
+
offered |= DropAcceptsText;
|
|
85
|
+
}
|
|
86
|
+
return offered;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
DragPayload payloadFrom(IDataObject *data) {
|
|
90
|
+
DragPayload payload;
|
|
91
|
+
if (data == nullptr) {
|
|
92
|
+
return payload;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
FORMATETC files{CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
|
96
|
+
STGMEDIUM medium{};
|
|
97
|
+
if (data->GetData(&files, &medium) == S_OK) {
|
|
98
|
+
auto drop = static_cast<HDROP>(GlobalLock(medium.hGlobal));
|
|
99
|
+
if (drop != nullptr) {
|
|
100
|
+
const UINT count = DragQueryFileW(drop, 0xFFFFFFFF, nullptr, 0);
|
|
101
|
+
for (UINT i = 0; i < count; i++) {
|
|
102
|
+
const UINT length = DragQueryFileW(drop, i, nullptr, 0);
|
|
103
|
+
std::wstring path(static_cast<size_t>(length) + 1, L'\0');
|
|
104
|
+
if (DragQueryFileW(drop, i, path.data(), length + 1) > 0) {
|
|
105
|
+
payload.files.push_back(narrow(path.c_str()));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
GlobalUnlock(medium.hGlobal);
|
|
109
|
+
}
|
|
110
|
+
ReleaseStgMedium(&medium);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
FORMATETC text{CF_UNICODETEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
|
114
|
+
STGMEDIUM textMedium{};
|
|
115
|
+
if (data->GetData(&text, &textMedium) == S_OK) {
|
|
116
|
+
auto *characters = static_cast<const wchar_t *>(GlobalLock(textMedium.hGlobal));
|
|
117
|
+
if (characters != nullptr) {
|
|
118
|
+
payload.text = narrow(characters);
|
|
119
|
+
GlobalUnlock(textMedium.hGlobal);
|
|
120
|
+
}
|
|
121
|
+
ReleaseStgMedium(&textMedium);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return payload;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// What OLE asks while a drag this app started is in flight.
|
|
128
|
+
class DragSource : public IDropSource {
|
|
129
|
+
public:
|
|
130
|
+
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **out) override {
|
|
131
|
+
if (out == nullptr) {
|
|
132
|
+
return E_POINTER;
|
|
133
|
+
}
|
|
134
|
+
if (riid == IID_IUnknown || riid == IID_IDropSource) {
|
|
135
|
+
*out = static_cast<IDropSource *>(this);
|
|
136
|
+
AddRef();
|
|
137
|
+
return S_OK;
|
|
138
|
+
}
|
|
139
|
+
*out = nullptr;
|
|
140
|
+
return E_NOINTERFACE;
|
|
141
|
+
}
|
|
142
|
+
ULONG STDMETHODCALLTYPE AddRef() override { return ++references_; }
|
|
143
|
+
ULONG STDMETHODCALLTYPE Release() override {
|
|
144
|
+
const ULONG left = --references_;
|
|
145
|
+
if (left == 0) {
|
|
146
|
+
delete this;
|
|
147
|
+
}
|
|
148
|
+
return left;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
HRESULT STDMETHODCALLTYPE QueryContinueDrag(BOOL escapePressed, DWORD keys) override {
|
|
152
|
+
if (escapePressed) {
|
|
153
|
+
return DRAGDROP_S_CANCEL;
|
|
154
|
+
}
|
|
155
|
+
// The button coming up is the drop. Left only: this platform starts a
|
|
156
|
+
// drag from a primary press and nothing else.
|
|
157
|
+
if ((keys & MK_LBUTTON) == 0) {
|
|
158
|
+
return DRAGDROP_S_DROP;
|
|
159
|
+
}
|
|
160
|
+
return S_OK;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
HRESULT STDMETHODCALLTYPE GiveFeedback(DWORD /*effect*/) override {
|
|
164
|
+
// The system's own cursors, which is what every other application shows.
|
|
165
|
+
return DRAGDROP_S_USEDEFAULTCURSORS;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private:
|
|
169
|
+
ULONG references_{1};
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// A data object carrying one thing.
|
|
173
|
+
//
|
|
174
|
+
// Hand-written because OLE has no simple one: even a single string needs an
|
|
175
|
+
// IDataObject, an IEnumFORMATETC to advertise it, and an HGLOBAL to hold it.
|
|
176
|
+
// GTK asks for a GValue and AppKit for an id<NSPasteboardWriting>; this is the
|
|
177
|
+
// same idea, three interfaces deep.
|
|
178
|
+
class SingleFormatData : public IDataObject {
|
|
179
|
+
public:
|
|
180
|
+
SingleFormatData(CLIPFORMAT format, HGLOBAL medium) : format_(format), medium_(medium) {}
|
|
181
|
+
|
|
182
|
+
~SingleFormatData() {
|
|
183
|
+
if (medium_ != nullptr) {
|
|
184
|
+
GlobalFree(medium_);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **out) override {
|
|
189
|
+
if (out == nullptr) {
|
|
190
|
+
return E_POINTER;
|
|
191
|
+
}
|
|
192
|
+
if (riid == IID_IUnknown || riid == IID_IDataObject) {
|
|
193
|
+
*out = static_cast<IDataObject *>(this);
|
|
194
|
+
AddRef();
|
|
195
|
+
return S_OK;
|
|
196
|
+
}
|
|
197
|
+
*out = nullptr;
|
|
198
|
+
return E_NOINTERFACE;
|
|
199
|
+
}
|
|
200
|
+
ULONG STDMETHODCALLTYPE AddRef() override { return ++references_; }
|
|
201
|
+
ULONG STDMETHODCALLTYPE Release() override {
|
|
202
|
+
const ULONG left = --references_;
|
|
203
|
+
if (left == 0) {
|
|
204
|
+
delete this;
|
|
205
|
+
}
|
|
206
|
+
return left;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
HRESULT STDMETHODCALLTYPE GetData(FORMATETC *request, STGMEDIUM *out) override {
|
|
210
|
+
if (request == nullptr || out == nullptr) {
|
|
211
|
+
return E_POINTER;
|
|
212
|
+
}
|
|
213
|
+
if (request->cfFormat != format_ || (request->tymed & TYMED_HGLOBAL) == 0) {
|
|
214
|
+
return DV_E_FORMATETC;
|
|
215
|
+
}
|
|
216
|
+
// A copy: the receiver owns what it is given, and this object may be
|
|
217
|
+
// asked again.
|
|
218
|
+
const SIZE_T size = GlobalSize(medium_);
|
|
219
|
+
HGLOBAL copy = GlobalAlloc(GMEM_MOVEABLE, size);
|
|
220
|
+
if (copy == nullptr) {
|
|
221
|
+
return E_OUTOFMEMORY;
|
|
222
|
+
}
|
|
223
|
+
void *from = GlobalLock(medium_);
|
|
224
|
+
void *to = GlobalLock(copy);
|
|
225
|
+
if (from != nullptr && to != nullptr) {
|
|
226
|
+
memcpy(to, from, size);
|
|
227
|
+
}
|
|
228
|
+
GlobalUnlock(medium_);
|
|
229
|
+
GlobalUnlock(copy);
|
|
230
|
+
|
|
231
|
+
out->tymed = TYMED_HGLOBAL;
|
|
232
|
+
out->hGlobal = copy;
|
|
233
|
+
out->pUnkForRelease = nullptr;
|
|
234
|
+
return S_OK;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
HRESULT STDMETHODCALLTYPE GetDataHere(FORMATETC *, STGMEDIUM *) override {
|
|
238
|
+
return E_NOTIMPL;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
HRESULT STDMETHODCALLTYPE QueryGetData(FORMATETC *request) override {
|
|
242
|
+
if (request == nullptr) {
|
|
243
|
+
return E_POINTER;
|
|
244
|
+
}
|
|
245
|
+
return (request->cfFormat == format_ && (request->tymed & TYMED_HGLOBAL) != 0)
|
|
246
|
+
? S_OK
|
|
247
|
+
: DV_E_FORMATETC;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
HRESULT STDMETHODCALLTYPE GetCanonicalFormatEtc(FORMATETC *, FORMATETC *out) override {
|
|
251
|
+
if (out != nullptr) {
|
|
252
|
+
out->ptd = nullptr;
|
|
253
|
+
}
|
|
254
|
+
return E_NOTIMPL;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
HRESULT STDMETHODCALLTYPE SetData(FORMATETC *, STGMEDIUM *, BOOL) override {
|
|
258
|
+
return E_NOTIMPL;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
HRESULT STDMETHODCALLTYPE EnumFormatEtc(DWORD direction, IEnumFORMATETC **out) override {
|
|
262
|
+
if (out == nullptr) {
|
|
263
|
+
return E_POINTER;
|
|
264
|
+
}
|
|
265
|
+
*out = nullptr;
|
|
266
|
+
if (direction != DATADIR_GET) {
|
|
267
|
+
return E_NOTIMPL;
|
|
268
|
+
}
|
|
269
|
+
FORMATETC format{format_, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
|
270
|
+
// SHCreateStdEnumFmtEtc rather than a fourth hand-written interface.
|
|
271
|
+
return SHCreateStdEnumFmtEtc(1, &format, out);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
HRESULT STDMETHODCALLTYPE DAdvise(FORMATETC *, DWORD, IAdviseSink *, DWORD *) override {
|
|
275
|
+
return OLE_E_ADVISENOTSUPPORTED;
|
|
276
|
+
}
|
|
277
|
+
HRESULT STDMETHODCALLTYPE DUnadvise(DWORD) override { return OLE_E_ADVISENOTSUPPORTED; }
|
|
278
|
+
HRESULT STDMETHODCALLTYPE EnumDAdvise(IEnumSTATDATA **) override {
|
|
279
|
+
return OLE_E_ADVISENOTSUPPORTED;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private:
|
|
283
|
+
ULONG references_{1};
|
|
284
|
+
CLIPFORMAT format_{0};
|
|
285
|
+
HGLOBAL medium_{nullptr};
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
IDataObject *makeTextDataObject(const std::string &text) {
|
|
289
|
+
const std::wstring wide = widen(text);
|
|
290
|
+
const SIZE_T bytes = (wide.size() + 1) * sizeof(wchar_t);
|
|
291
|
+
HGLOBAL medium = GlobalAlloc(GMEM_MOVEABLE, bytes);
|
|
292
|
+
if (medium == nullptr) {
|
|
293
|
+
return nullptr;
|
|
294
|
+
}
|
|
295
|
+
auto *out = static_cast<wchar_t *>(GlobalLock(medium));
|
|
296
|
+
if (out != nullptr) {
|
|
297
|
+
memcpy(out, wide.c_str(), bytes);
|
|
298
|
+
GlobalUnlock(medium);
|
|
299
|
+
}
|
|
300
|
+
return new SingleFormatData(CF_UNICODETEXT, medium);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
IDataObject *makeFileDataObject(const std::string &path) {
|
|
304
|
+
const std::wstring wide = widen(path);
|
|
305
|
+
// DROPFILES, then the paths, then a second NUL to end the list. The shell's
|
|
306
|
+
// own format, which is what makes another application receive a file rather
|
|
307
|
+
// than a string that looks like one.
|
|
308
|
+
const SIZE_T bytes = sizeof(DROPFILES) + (wide.size() + 2) * sizeof(wchar_t);
|
|
309
|
+
HGLOBAL medium = GlobalAlloc(GMEM_MOVEABLE, bytes);
|
|
310
|
+
if (medium == nullptr) {
|
|
311
|
+
return nullptr;
|
|
312
|
+
}
|
|
313
|
+
auto *drop = static_cast<DROPFILES *>(GlobalLock(medium));
|
|
314
|
+
if (drop != nullptr) {
|
|
315
|
+
ZeroMemory(drop, bytes);
|
|
316
|
+
drop->pFiles = sizeof(DROPFILES);
|
|
317
|
+
drop->fWide = TRUE;
|
|
318
|
+
auto *names = reinterpret_cast<wchar_t *>(reinterpret_cast<char *>(drop) + sizeof(DROPFILES));
|
|
319
|
+
memcpy(names, wide.c_str(), (wide.size() + 1) * sizeof(wchar_t));
|
|
320
|
+
GlobalUnlock(medium);
|
|
321
|
+
}
|
|
322
|
+
return new SingleFormatData(CF_HDROP, medium);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// The OLE drop target for one window.
|
|
326
|
+
//
|
|
327
|
+
// Reference counted because OLE holds it: `RegisterDragDrop` takes a
|
|
328
|
+
// reference and `RevokeDragDrop` drops it, which is why detachDropTarget has
|
|
329
|
+
// to run before the window is destroyed.
|
|
330
|
+
class DropTarget : public IDropTarget {
|
|
331
|
+
public:
|
|
332
|
+
DropTarget(HWND window, win32::RnWin32View *root) : window_(window), root_(root) {}
|
|
333
|
+
|
|
334
|
+
// --- IUnknown ---
|
|
335
|
+
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **out) override {
|
|
336
|
+
if (out == nullptr) {
|
|
337
|
+
return E_POINTER;
|
|
338
|
+
}
|
|
339
|
+
if (riid == IID_IUnknown || riid == IID_IDropTarget) {
|
|
340
|
+
*out = static_cast<IDropTarget *>(this);
|
|
341
|
+
AddRef();
|
|
342
|
+
return S_OK;
|
|
343
|
+
}
|
|
344
|
+
*out = nullptr;
|
|
345
|
+
return E_NOINTERFACE;
|
|
346
|
+
}
|
|
347
|
+
ULONG STDMETHODCALLTYPE AddRef() override { return ++references_; }
|
|
348
|
+
ULONG STDMETHODCALLTYPE Release() override {
|
|
349
|
+
const ULONG left = --references_;
|
|
350
|
+
if (left == 0) {
|
|
351
|
+
delete this;
|
|
352
|
+
}
|
|
353
|
+
return left;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// --- IDropTarget ---
|
|
357
|
+
HRESULT STDMETHODCALLTYPE DragEnter(IDataObject *data,
|
|
358
|
+
DWORD /*keys*/,
|
|
359
|
+
POINTL point,
|
|
360
|
+
DWORD *effect) override {
|
|
361
|
+
offered_ = offeredBy(data);
|
|
362
|
+
data_ = data;
|
|
363
|
+
return update(point, effect);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
HRESULT STDMETHODCALLTYPE DragOver(DWORD /*keys*/, POINTL point, DWORD *effect) override {
|
|
367
|
+
return update(point, effect);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
HRESULT STDMETHODCALLTYPE DragLeave() override {
|
|
371
|
+
reportLeave(0.0, 0.0);
|
|
372
|
+
offered_ = DropAcceptsNone;
|
|
373
|
+
data_ = nullptr;
|
|
374
|
+
return S_OK;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
HRESULT STDMETHODCALLTYPE Drop(IDataObject *data,
|
|
378
|
+
DWORD /*keys*/,
|
|
379
|
+
POINTL point,
|
|
380
|
+
DWORD *effect) override {
|
|
381
|
+
const DragPayload payload = payloadFrom(data);
|
|
382
|
+
// Asked again from the contents, for the reason the other two hosts give:
|
|
383
|
+
// this is the moment they are known.
|
|
384
|
+
std::uint16_t carries = DropAcceptsNone;
|
|
385
|
+
if (payload.hasFiles()) {
|
|
386
|
+
carries |= DropAcceptsFiles;
|
|
387
|
+
}
|
|
388
|
+
if (payload.hasText()) {
|
|
389
|
+
carries |= DropAcceptsText;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const POINT local = toClient(point);
|
|
393
|
+
const facebook::react::Tag found =
|
|
394
|
+
dropTargetAt(root_, local.x, local.y, carries);
|
|
395
|
+
current_ = 0;
|
|
396
|
+
data_ = nullptr;
|
|
397
|
+
if (found == 0) {
|
|
398
|
+
if (effect != nullptr) {
|
|
399
|
+
*effect = DROPEFFECT_NONE;
|
|
400
|
+
}
|
|
401
|
+
return S_OK;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
DropEvent event;
|
|
405
|
+
event.tag = found;
|
|
406
|
+
event.phase = DropPhase::Drop;
|
|
407
|
+
event.x = local.x;
|
|
408
|
+
event.y = local.y;
|
|
409
|
+
event.payload = payload;
|
|
410
|
+
reportDrop(event);
|
|
411
|
+
if (effect != nullptr) {
|
|
412
|
+
*effect = DROPEFFECT_COPY;
|
|
413
|
+
}
|
|
414
|
+
return S_OK;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
private:
|
|
418
|
+
POINT toClient(POINTL point) const {
|
|
419
|
+
POINT local{point.x, point.y};
|
|
420
|
+
ScreenToClient(window_, &local);
|
|
421
|
+
return local;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
void reportLeave(double x, double y) {
|
|
425
|
+
if (current_ == 0) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
DropEvent event;
|
|
429
|
+
event.tag = current_;
|
|
430
|
+
event.phase = DropPhase::Leave;
|
|
431
|
+
event.x = x;
|
|
432
|
+
event.y = y;
|
|
433
|
+
basalt::reportDrop(event);
|
|
434
|
+
current_ = 0;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
HRESULT update(POINTL point, DWORD *effect) {
|
|
438
|
+
const POINT local = toClient(point);
|
|
439
|
+
const facebook::react::Tag found = dropTargetAt(root_, local.x, local.y, offered_);
|
|
440
|
+
|
|
441
|
+
if (found != current_) {
|
|
442
|
+
reportLeave(local.x, local.y);
|
|
443
|
+
}
|
|
444
|
+
if (found == 0) {
|
|
445
|
+
if (effect != nullptr) {
|
|
446
|
+
*effect = DROPEFFECT_NONE;
|
|
447
|
+
}
|
|
448
|
+
return S_OK;
|
|
449
|
+
}
|
|
450
|
+
current_ = found;
|
|
451
|
+
|
|
452
|
+
DropEvent event;
|
|
453
|
+
event.tag = found;
|
|
454
|
+
event.phase = DropPhase::Over;
|
|
455
|
+
event.x = local.x;
|
|
456
|
+
event.y = local.y;
|
|
457
|
+
basalt::reportDrop(event);
|
|
458
|
+
if (effect != nullptr) {
|
|
459
|
+
*effect = DROPEFFECT_COPY;
|
|
460
|
+
}
|
|
461
|
+
return S_OK;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
ULONG references_{1};
|
|
465
|
+
HWND window_{nullptr};
|
|
466
|
+
win32::RnWin32View *root_{nullptr};
|
|
467
|
+
IDataObject *data_{nullptr};
|
|
468
|
+
std::uint16_t offered_{DropAcceptsNone};
|
|
469
|
+
facebook::react::Tag current_{0};
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
} // namespace
|
|
473
|
+
|
|
474
|
+
facebook::react::Tag dropTargetAt(win32::RnWin32View *root,
|
|
475
|
+
double x,
|
|
476
|
+
double y,
|
|
477
|
+
std::uint16_t accepts) {
|
|
478
|
+
if (root == nullptr) {
|
|
479
|
+
return 0;
|
|
480
|
+
}
|
|
481
|
+
win32::RnWin32View *hit =
|
|
482
|
+
win32::hitTest(root, static_cast<float>(x), static_cast<float>(y));
|
|
483
|
+
if (hit == nullptr) {
|
|
484
|
+
return 0;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
std::vector<win32::RnWin32View *> path;
|
|
488
|
+
if (!pathTo(root, hit, path)) {
|
|
489
|
+
return 0;
|
|
490
|
+
}
|
|
491
|
+
// Deepest first, which is the rule: a drop target inside another wins.
|
|
492
|
+
for (auto it = path.rbegin(); it != path.rend(); ++it) {
|
|
493
|
+
const std::uint16_t wanted = dropAcceptsFrom((*it)->nativeId());
|
|
494
|
+
if (wanted != DropAcceptsNone && (wanted & accepts) != 0) {
|
|
495
|
+
return static_cast<facebook::react::Tag>((*it)->tag());
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return 0;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
DragPayload dragPayloadAt(win32::RnWin32View *root, double x, double y) {
|
|
502
|
+
if (root == nullptr) {
|
|
503
|
+
return {};
|
|
504
|
+
}
|
|
505
|
+
win32::RnWin32View *hit =
|
|
506
|
+
win32::hitTest(root, static_cast<float>(x), static_cast<float>(y));
|
|
507
|
+
if (hit == nullptr) {
|
|
508
|
+
return {};
|
|
509
|
+
}
|
|
510
|
+
std::vector<win32::RnWin32View *> path;
|
|
511
|
+
if (!pathTo(root, hit, path)) {
|
|
512
|
+
return {};
|
|
513
|
+
}
|
|
514
|
+
// Deepest first, the same rule the drop side applies.
|
|
515
|
+
for (auto it = path.rbegin(); it != path.rend(); ++it) {
|
|
516
|
+
const DragPayload payload = dragPayloadFrom((*it)->nativeId());
|
|
517
|
+
if (!payload.empty()) {
|
|
518
|
+
return payload;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return {};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
bool beginDragIfMarked(win32::RnWin32View *root, double x, double y) {
|
|
525
|
+
const DragPayload payload = dragPayloadAt(root, x, y);
|
|
526
|
+
if (payload.empty()) {
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
IDataObject *data = nullptr;
|
|
531
|
+
if (payload.hasFiles()) {
|
|
532
|
+
data = makeFileDataObject(payload.files.front());
|
|
533
|
+
} else {
|
|
534
|
+
data = makeTextDataObject(payload.text);
|
|
535
|
+
}
|
|
536
|
+
if (data == nullptr) {
|
|
537
|
+
return false;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
auto *source = new DragSource();
|
|
541
|
+
DWORD effect = DROPEFFECT_NONE;
|
|
542
|
+
// Modal: this does not return until the drop or the cancel. Which is why
|
|
543
|
+
// the caller has to treat the gesture as gone -- everything that would have
|
|
544
|
+
// continued the press happens inside here, to OLE.
|
|
545
|
+
DoDragDrop(data, source, DROPEFFECT_COPY, &effect);
|
|
546
|
+
source->Release();
|
|
547
|
+
data->Release();
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
void attachDropTarget(HWND window, win32::RnWin32View *root) {
|
|
552
|
+
if (window == nullptr || root == nullptr) {
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
auto *target = new DropTarget(window, root);
|
|
556
|
+
// OLE takes its own reference; this one is ours to drop either way.
|
|
557
|
+
RegisterDragDrop(window, target);
|
|
558
|
+
target->Release();
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
void detachDropTarget(HWND window) {
|
|
562
|
+
if (window != nullptr) {
|
|
563
|
+
RevokeDragDrop(window);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
} // namespace basalt
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// The Win32 half of drag and drop: what happens when something is dragged onto
|
|
2
|
+
// the window.
|
|
3
|
+
//
|
|
4
|
+
// See core/DragAndDrop.h for the model, which is shared, and for why a view
|
|
5
|
+
// declares itself a drop target by its nativeID.
|
|
6
|
+
|
|
7
|
+
#pragma once
|
|
8
|
+
|
|
9
|
+
#include "DragAndDrop.h"
|
|
10
|
+
#include "RnWin32View.h"
|
|
11
|
+
|
|
12
|
+
#include <windows.h>
|
|
13
|
+
|
|
14
|
+
#include <cstdint>
|
|
15
|
+
|
|
16
|
+
#include <react/renderer/core/ReactPrimitives.h>
|
|
17
|
+
|
|
18
|
+
namespace basalt {
|
|
19
|
+
|
|
20
|
+
// The deepest view under this point that would take what is being dragged, or
|
|
21
|
+
// 0 for none. The point is in the root's coordinates.
|
|
22
|
+
facebook::react::Tag dropTargetAt(win32::RnWin32View *root,
|
|
23
|
+
double x,
|
|
24
|
+
double y,
|
|
25
|
+
std::uint16_t accepts);
|
|
26
|
+
|
|
27
|
+
// Registers an OLE drop target for this window, so it accepts files and text
|
|
28
|
+
// dragged onto it. Balanced by `detachDropTarget` at teardown: a window that
|
|
29
|
+
// is destroyed while registered leaves OLE holding a pointer to it.
|
|
30
|
+
void attachDropTarget(HWND window, win32::RnWin32View *root);
|
|
31
|
+
void detachDropTarget(HWND window);
|
|
32
|
+
|
|
33
|
+
// The payload a marked view under this point represents, or an empty one.
|
|
34
|
+
DragPayload dragPayloadAt(win32::RnWin32View *root, double x, double y);
|
|
35
|
+
|
|
36
|
+
// Begins a drag out of the window if the press began on a marked view. True
|
|
37
|
+
// when it did -- and DoDragDrop runs its own modal loop until the drop, so a
|
|
38
|
+
// caller that hears yes has already lost the gesture and should cancel the
|
|
39
|
+
// touch it was tracking.
|
|
40
|
+
bool beginDragIfMarked(win32::RnWin32View *root, double x, double y);
|
|
41
|
+
|
|
42
|
+
} // namespace basalt
|