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,232 @@
|
|
|
1
|
+
// <TextInput> on Win32.
|
|
2
|
+
//
|
|
3
|
+
// The native half is React Native's own *iOS* TextInput: its shadow node,
|
|
4
|
+
// props, state and event emitter are pure C++ and measure through a
|
|
5
|
+
// TextLayoutManager, which on this platform is the DirectWrite one. Android's
|
|
6
|
+
// variant includes fbjni and calls into a Java FabricUIManager, so it is
|
|
7
|
+
// unusable here; see docs/DECISIONS.md. The component name is therefore
|
|
8
|
+
// "TextInput", which is what packages/basalt-core's
|
|
9
|
+
// src/overrides/TextInput.js asks for -- the same file both other desktops use,
|
|
10
|
+
// because that override was never about Linux.
|
|
11
|
+
//
|
|
12
|
+
// The editing itself is a real EDIT control, not a caret drawn on a DirectWrite
|
|
13
|
+
// layout. That brings the IME, selection, the clipboard, undo, double-click
|
|
14
|
+
// word selection, Ctrl+arrow, and every other binding a Windows user expects --
|
|
15
|
+
// none of it worth reimplementing and all of it easy to get subtly wrong. GTK
|
|
16
|
+
// embeds a GtkText and AppKit an NSTextField for exactly the same reason.
|
|
17
|
+
//
|
|
18
|
+
// ## The peer is a window, and that is the whole difficulty
|
|
19
|
+
//
|
|
20
|
+
// On the other two desktops a React Native view *is* a widget, so a text field
|
|
21
|
+
// is a widget inside a widget and the toolkit handles the rest. Here every view
|
|
22
|
+
// is a plain C++ object painted with Direct2D into one HWND, and an EDIT is a
|
|
23
|
+
// real child window. Three consequences follow, and none of them has an
|
|
24
|
+
// equivalent on GTK or AppKit:
|
|
25
|
+
//
|
|
26
|
+
// - **It has to be positioned by hand.** The manager walks the view up to the
|
|
27
|
+
// surface root to find its rectangle in client coordinates, and has to redo
|
|
28
|
+
// that whenever anything moves it -- which includes a scroll, where no
|
|
29
|
+
// mutation arrives at all. `syncBounds` is that, and the host calls it after
|
|
30
|
+
// every transaction and every wheel.
|
|
31
|
+
//
|
|
32
|
+
// - **It is always on top.** Child windows paint above whatever Direct2D drew,
|
|
33
|
+
// in an order the React tree has no say in. A field cannot be covered by a
|
|
34
|
+
// later sibling, and it does not clip to a scrolled ancestor -- it is hidden
|
|
35
|
+
// when it leaves the ancestor's box instead, which is close enough to look
|
|
36
|
+
// right and is not the same thing.
|
|
37
|
+
//
|
|
38
|
+
// - **Its events arrive at the parent.** EN_CHANGE, EN_SETFOCUS and
|
|
39
|
+
// EN_KILLFOCUS are WM_COMMAND notifications to the host window, and the
|
|
40
|
+
// text and background colours are answered from WM_CTLCOLOREDIT. So the
|
|
41
|
+
// host forwards both, which is why this class has methods a mounting
|
|
42
|
+
// manager would not otherwise need.
|
|
43
|
+
//
|
|
44
|
+
// ## The controlled-value loop, which is not about a toolkit
|
|
45
|
+
//
|
|
46
|
+
// React Native's <TextInput> is controlled: JavaScript owns the value, the
|
|
47
|
+
// control reports every change, JavaScript re-renders and pushes the value back
|
|
48
|
+
// down as a prop. If applying that prop looks like the user typing, the two
|
|
49
|
+
// chase each other forever. If a prop that arrives late is applied anyway, a
|
|
50
|
+
// fast typist watches characters reorder themselves.
|
|
51
|
+
//
|
|
52
|
+
// `applying` breaks the first and `eventCount` the second, exactly as on both
|
|
53
|
+
// other desktops, because neither is a Windows problem.
|
|
54
|
+
|
|
55
|
+
#pragma once
|
|
56
|
+
|
|
57
|
+
#include "RnWin32View.h"
|
|
58
|
+
|
|
59
|
+
#include <react/renderer/components/iostextinput/TextInputShadowNode.h>
|
|
60
|
+
#include <react/renderer/components/textinput/TextInputEventEmitter.h>
|
|
61
|
+
#include <react/renderer/core/EventEmitter.h>
|
|
62
|
+
#include <react/renderer/mounting/ShadowView.h>
|
|
63
|
+
|
|
64
|
+
#include <windows.h>
|
|
65
|
+
|
|
66
|
+
#include <functional>
|
|
67
|
+
#include <string>
|
|
68
|
+
#include <unordered_map>
|
|
69
|
+
|
|
70
|
+
namespace basalt {
|
|
71
|
+
|
|
72
|
+
class Win32TextInputManager {
|
|
73
|
+
public:
|
|
74
|
+
using EmitterLookup = std::function<facebook::react::EventEmitter::Shared(facebook::react::Tag)>;
|
|
75
|
+
|
|
76
|
+
explicit Win32TextInputManager(EmitterLookup lookup);
|
|
77
|
+
~Win32TextInputManager();
|
|
78
|
+
|
|
79
|
+
Win32TextInputManager(const Win32TextInputManager &) = delete;
|
|
80
|
+
Win32TextInputManager &operator=(const Win32TextInputManager &) = delete;
|
|
81
|
+
Win32TextInputManager(Win32TextInputManager &&) = delete;
|
|
82
|
+
Win32TextInputManager &operator=(Win32TextInputManager &&) = delete;
|
|
83
|
+
|
|
84
|
+
// The window every peer is a child of. Nothing can be created before this is
|
|
85
|
+
// set, which is why `update` tolerates being called without it: the mounting
|
|
86
|
+
// manager exists before the host's window does, and the tests never have one.
|
|
87
|
+
void setHostWindow(HWND window);
|
|
88
|
+
|
|
89
|
+
// Called for every mutation touching a TextInput. Creates the control the
|
|
90
|
+
// first time, then applies props.
|
|
91
|
+
void update(win32::RnWin32View *view, const facebook::react::ShadowView &shadowView);
|
|
92
|
+
|
|
93
|
+
void remove(facebook::react::Tag tag);
|
|
94
|
+
|
|
95
|
+
// focus, blur, and setTextAndSelection. Returns false for anything else.
|
|
96
|
+
bool dispatchCommand(facebook::react::Tag tag,
|
|
97
|
+
const std::string &name,
|
|
98
|
+
const folly::dynamic &args);
|
|
99
|
+
|
|
100
|
+
// Moves every peer to where its view now is, in client coordinates, and hides
|
|
101
|
+
// the ones that have gone out of sight. Called after each transaction and
|
|
102
|
+
// each scroll: a child window does not follow a view that has no widget.
|
|
103
|
+
void syncBounds(win32::RnWin32View *root);
|
|
104
|
+
|
|
105
|
+
// WM_COMMAND from the host: EN_CHANGE, EN_SETFOCUS, EN_KILLFOCUS. True when
|
|
106
|
+
// the notification was one of this manager's controls.
|
|
107
|
+
bool handleControlCommand(WPARAM wparam, LPARAM lparam);
|
|
108
|
+
|
|
109
|
+
// WM_CTLCOLOREDIT / WM_CTLCOLORSTATIC from the host. Returns the brush to
|
|
110
|
+
// paint the control's background with, or null when the control is not one of
|
|
111
|
+
// this manager's. An EDIT otherwise draws in the system's colours, which have
|
|
112
|
+
// nothing to do with the `style` the component was given -- on a dark field
|
|
113
|
+
// that is dark text on dark, the same trap GtkText has.
|
|
114
|
+
HBRUSH controlColor(HDC deviceContext, HWND control);
|
|
115
|
+
|
|
116
|
+
// True when the control is one of this manager's peers, which is how the host
|
|
117
|
+
// tells a key meant for a field from one meant for the window.
|
|
118
|
+
bool ownsControl(HWND control) const;
|
|
119
|
+
|
|
120
|
+
// Focuses the field under a point in the surface root's coordinates, if there
|
|
121
|
+
// is one. True when there was.
|
|
122
|
+
//
|
|
123
|
+
// Only BASALT_TEST_TAP needs this, and the reason is the one thing a
|
|
124
|
+
// synthesised tap cannot reproduce. A real click at these coordinates never
|
|
125
|
+
// reaches the host's window procedure at all: the peer is a child window, so
|
|
126
|
+
// USER32 routes the click to it and the control focuses itself. A tap
|
|
127
|
+
// injected at the touch dispatcher skips all of that, because the dispatcher
|
|
128
|
+
// is above the point where the two paths diverge.
|
|
129
|
+
bool focusAt(win32::RnWin32View *root, double x, double y);
|
|
130
|
+
|
|
131
|
+
// Types into whichever field has focus, for BASALT_TEST_TYPE. Real WM_CHAR
|
|
132
|
+
// messages to the real control, so what it skips is the keyboard driver and
|
|
133
|
+
// nothing above it.
|
|
134
|
+
bool typeIntoFocused(const std::string &text);
|
|
135
|
+
|
|
136
|
+
private:
|
|
137
|
+
struct Entry {
|
|
138
|
+
win32::RnWin32View *view{nullptr};
|
|
139
|
+
HWND control{nullptr};
|
|
140
|
+
HFONT font{nullptr};
|
|
141
|
+
facebook::react::Tag tag{0};
|
|
142
|
+
Win32TextInputManager *owner{nullptr};
|
|
143
|
+
// The subclass has to be able to find its way back, and SetWindowSubclass's
|
|
144
|
+
// reference data is the only channel; this is what is put there.
|
|
145
|
+
|
|
146
|
+
// React Native counts events so it can ignore a prop update older than what
|
|
147
|
+
// the user has since typed. Every emitted metric carries it.
|
|
148
|
+
int eventCount{0};
|
|
149
|
+
|
|
150
|
+
// True while a prop is being pushed into the control, so the EN_CHANGE it
|
|
151
|
+
// provokes is not reported back as the user typing.
|
|
152
|
+
bool applying{false};
|
|
153
|
+
// The selection last reported to JavaScript. A plain EDIT has no
|
|
154
|
+
// notification for the caret moving -- EN_SELCHANGE belongs to RichEdit --
|
|
155
|
+
// so the selection is read after anything that could have moved it and
|
|
156
|
+
// compared with this. See reportSelectionIfChanged.
|
|
157
|
+
facebook::react::AttributedString::Range lastReportedSelection{};
|
|
158
|
+
|
|
159
|
+
std::string lastReportedText;
|
|
160
|
+
|
|
161
|
+
// The last `text` prop actually seen, and whether one has been seen at all.
|
|
162
|
+
//
|
|
163
|
+
// This is what tells a *controlled* field from an uncontrolled one, which
|
|
164
|
+
// props alone cannot: React Native's TextInput.js sends
|
|
165
|
+
// `text={value ?? defaultValue}`, and an uncontrolled field with no default
|
|
166
|
+
// sends undefined, which arrives here as the empty string -- exactly what a
|
|
167
|
+
// controlled field that JavaScript has cleared sends. Applying it on every
|
|
168
|
+
// render therefore wipes an uncontrolled field the moment anything else in
|
|
169
|
+
// the tree re-renders, because RN re-sends `mostRecentEventCount` on every
|
|
170
|
+
// change and that alone produces an Update mutation.
|
|
171
|
+
//
|
|
172
|
+
// So the prop is applied when it *changes*, not when it differs from the
|
|
173
|
+
// control. A controlled field's value changes as the user types; an
|
|
174
|
+
// uncontrolled one's never does.
|
|
175
|
+
std::string lastPropText;
|
|
176
|
+
bool sawProps{false};
|
|
177
|
+
|
|
178
|
+
COLORREF textColor{RGB(0, 0, 0)};
|
|
179
|
+
COLORREF backgroundColor{RGB(255, 255, 255)};
|
|
180
|
+
HBRUSH backgroundBrush{nullptr};
|
|
181
|
+
// Whether the view behind the control has a background of its own. With
|
|
182
|
+
// none, the control is painted over whatever Direct2D drew there -- which
|
|
183
|
+
// it cannot see, so the closest honest answer is the window's own ground.
|
|
184
|
+
bool hasBackground{false};
|
|
185
|
+
|
|
186
|
+
bool secure{false};
|
|
187
|
+
|
|
188
|
+
// Yoga resolves border and padding into contentInsets, so the same twelve
|
|
189
|
+
// points mean the same thing in a field as in a <View>. Applied by placing
|
|
190
|
+
// the control inside them rather than by any EDIT message: EM_SETRECT is
|
|
191
|
+
// documented as multiline-only and silently does nothing here, which is a
|
|
192
|
+
// afternoon nobody should spend twice.
|
|
193
|
+
RECT insets{0, 0, 0, 0};
|
|
194
|
+
|
|
195
|
+
// What the control's font actually occupies, measured rather than assumed:
|
|
196
|
+
// a family substitution changes it, and this is what the control's height
|
|
197
|
+
// is set to so that the text sits in the middle of the field.
|
|
198
|
+
LONG lineHeight{0};
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
Entry *entryForControl(HWND control);
|
|
202
|
+
void applyProps(Entry &entry, const facebook::react::TextInputProps &props);
|
|
203
|
+
|
|
204
|
+
// Remembers the content box and the line height, which is what `syncBounds`
|
|
205
|
+
// needs to place the control and cannot work out from the view alone.
|
|
206
|
+
void measureShape(Entry &entry, const facebook::react::LayoutMetrics &metrics);
|
|
207
|
+
|
|
208
|
+
void destroyPeer(Entry &entry);
|
|
209
|
+
|
|
210
|
+
// Enter, and the keys an EDIT would otherwise beep at. Installed with
|
|
211
|
+
// SetWindowSubclass rather than SetWindowLongPtr so that a later subclass by
|
|
212
|
+
// anything else still chains correctly.
|
|
213
|
+
static LRESULT CALLBACK
|
|
214
|
+
editProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam, UINT_PTR id, DWORD_PTR data);
|
|
215
|
+
|
|
216
|
+
void reportChange(Entry &entry);
|
|
217
|
+
facebook::react::TextInputEventEmitter::Metrics metricsFor(const Entry &entry) const;
|
|
218
|
+
// Reads the caret and emits onSelectionChange if it moved. Called after any
|
|
219
|
+
// message that could have moved it, because Windows will not say.
|
|
220
|
+
void reportSelectionIfChanged(Entry &entry);
|
|
221
|
+
std::shared_ptr<const facebook::react::TextInputEventEmitter> emitterFor(
|
|
222
|
+
facebook::react::Tag tag) const;
|
|
223
|
+
|
|
224
|
+
EmitterLookup lookup_;
|
|
225
|
+
HWND host_{nullptr};
|
|
226
|
+
// Stable addresses: the subclass procedure holds an Entry pointer, so the map
|
|
227
|
+
// must not move its values. std::unordered_map does not, which is the reason
|
|
228
|
+
// it is this container and not a vector.
|
|
229
|
+
std::unordered_map<facebook::react::Tag, Entry> entries_;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
} // namespace basalt
|