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,153 @@
1
+ // Win32 mouse messages into React Native's touch events.
2
+ //
3
+ // React Native's Pressability -- what backs <Pressable>, <TouchableOpacity> and
4
+ // every onPress in an app -- runs on the responder system in JavaScript, and the
5
+ // responder system is fed by touchstart/touchmove/touchend. So a pointer on a
6
+ // desktop is reported here as a single touch point, which is also what React
7
+ // Native for Windows and macOS do. W3C pointer events exist alongside these and
8
+ // are only consulted for hover, behind a feature flag.
9
+ //
10
+ // Deliberately the same shape as the GTK and AppKit dispatchers, down to the
11
+ // state machine and the event construction, because the part that is easy to
12
+ // get subtly different -- which touches are in `touches` versus
13
+ // `changedTouches`, and which view a gesture is reported against -- is not
14
+ // about the toolkit at all. If the three ever diverge, an app's onPress will
15
+ // fire on two desktops and not the third.
16
+ //
17
+ // What is different here is what the toolkit does *not* provide. GTK has
18
+ // `gtk_widget_pick` and AppKit has `-hitTest:`, so on those two the dispatcher
19
+ // asks the toolkit which widget was pressed. A Win32 view is a plain C++
20
+ // object with no window of its own, so the whole of hit testing is this
21
+ // project's: `hitTest` in RnWin32View.h, which this calls. The same is true of
22
+ // capture -- AppKit and GTK route the drag to the widget that took the press
23
+ // for free, and here the host has to call SetCapture.
24
+
25
+ #pragma once
26
+
27
+ #include "HoverTracker.h"
28
+ #include "PointerButtons.h"
29
+ #include "RnWin32View.h"
30
+ #include "Win32MountingManager.h"
31
+
32
+ #include <react/renderer/components/view/TouchEventEmitter.h>
33
+ #include <react/renderer/graphics/Point.h>
34
+
35
+ namespace basalt {
36
+
37
+ // The tag of the React Native view under a point, in the surface root's
38
+ // coordinates, or 0 if the point hits nothing. The view-tree half of this is
39
+ // `win32::hitTest`, which lives in the view layer and needs no React Native.
40
+ facebook::react::Tag hitTestTag(win32::RnWin32View *root, double x, double y);
41
+
42
+ class Win32TouchDispatcher {
43
+ public:
44
+ Win32TouchDispatcher(Win32MountingManager *mountingManager,
45
+ win32::RnWin32View *surfaceRoot);
46
+
47
+ Win32TouchDispatcher(const Win32TouchDispatcher &) = delete;
48
+ Win32TouchDispatcher &operator=(const Win32TouchDispatcher &) = delete;
49
+ Win32TouchDispatcher(Win32TouchDispatcher &&) = delete;
50
+ Win32TouchDispatcher &operator=(Win32TouchDispatcher &&) = delete;
51
+
52
+ // The surface root can be replaced after a surface restart; the dispatcher
53
+ // outlives it because the host owns both and tears them down together.
54
+ //
55
+ // The hover state goes with it. Every tag in the old tree is about to stop
56
+ // existing, and a leave dispatched at one of them would be a lookup of an
57
+ // emitter that is gone.
58
+ void setSurfaceRoot(win32::RnWin32View *surfaceRoot) {
59
+ surfaceRoot_ = surfaceRoot;
60
+ hover_.forget();
61
+ }
62
+
63
+ // Synthesises a press and release at a point in surface-root coordinates,
64
+ // entering at the same place the window procedure does.
65
+ //
66
+ // This exists because the input path is otherwise untestable in automation:
67
+ // synthesising a real mouse event on Windows means SendInput, which moves the
68
+ // actual cursor and so cannot run beside anything else on the machine. It
69
+ // skips Win32's event delivery and nothing else, so it proves hit testing,
70
+ // emitter lookup and event-beat delivery, but not that Windows routes clicks
71
+ // here.
72
+ void synthesiseTap(double x, double y);
73
+ // The same, with a button. BASALT_TEST_SECONDARY_TAP is what needs it: a
74
+ // right-click cannot be injected any other way, and it is the one click whose
75
+ // whole point is that it does *not* press what it lands on.
76
+ void synthesiseTap(double x, double y, basalt::PointerButton button);
77
+
78
+ // A press, a run of moves, and a release. The same reason as synthesiseTap,
79
+ // one step further: a gesture recogniser cannot be exercised by a tap at all
80
+ // -- a pan is defined by the movement between the two -- so a drag has to be
81
+ // injectable for anything about it to be provable outside a person's hand.
82
+ void synthesiseDrag(double fromX, double fromY, double toX, double toY, int steps);
83
+
84
+ // Moves the pointer without pressing it, which is what produces hover. Same
85
+ // reason as the two above, and the same entry point WM_MOUSEMOVE uses. A
86
+ // negative coordinate means the pointer left the window.
87
+ void synthesiseHover(double x, double y);
88
+
89
+ // The UI-thread half, called by the host's window procedure. Coordinates are
90
+ // client-area pixels, which are the surface root's own coordinates: the host
91
+ // sizes the root to the client rectangle, so the two spaces are the same one.
92
+ // `button` decides whether this presses anything. Only the primary one drives
93
+ // the touch model; a secondary or middle click produces a pointer event and
94
+ // nothing else. See core/PointerButtons.h.
95
+ void dispatchTouchStart(double x, double y, basalt::PointerButton button);
96
+ void dispatchTouchMove(double x, double y);
97
+ void dispatchTouchEnd(double x, double y, basalt::PointerButton button);
98
+ void dispatchTouchCancel();
99
+
100
+ // The hover half. Separate from dispatchTouchMove because the two answer
101
+ // different questions about the same WM_MOUSEMOVE: the touch model asks which
102
+ // view the finger went down on, and hover asks which views the cursor is
103
+ // inside right now.
104
+ void dispatchHover(double x, double y);
105
+ void dispatchHoverLeave();
106
+
107
+ // Whether a press is outstanding. The host reads this to decide whether a
108
+ // WM_MOUSEMOVE is a drag worth reporting and whether to release capture.
109
+ bool isDown() const { return isDown_; }
110
+
111
+ private:
112
+ enum class TouchKind { Start, Move, End, Cancel };
113
+
114
+ // Hands the pointer to a gesture recogniser that has activated, cancelling
115
+ // React Native's touch. True when that happened, which means the caller has
116
+ // nothing left to report.
117
+ bool yieldToGesture(double x, double y);
118
+
119
+ // Builds the TouchEvent for the current gesture. React Native wants three
120
+ // lists -- all touches, the ones that changed, and the ones that started on
121
+ // the event target -- which with one pointer are the same list, except on
122
+ // touchend where nothing is touching any more.
123
+ void emit(TouchKind kind, facebook::react::Tag target, double x, double y);
124
+
125
+ // A pointerMove at the view under the cursor. The only pointer event a host
126
+ // emits for hover; see core/HoverTracker.h.
127
+ // A press or a release as a pointer event, which is the only form a
128
+ // non-primary click arrives in.
129
+ void emitPointerButton(bool down,
130
+ facebook::react::Tag target,
131
+ double originX,
132
+ double originY,
133
+ double x,
134
+ double y,
135
+ basalt::PointerButton button);
136
+ void emitPointerMove(
137
+ facebook::react::Tag target, double originX, double originY, double x, double y);
138
+
139
+ Win32MountingManager *mountingManager_;
140
+ win32::RnWin32View *surfaceRoot_;
141
+
142
+ // The view a gesture started on. React Native reports every touch in a
143
+ // gesture against the target it began on, even after the pointer leaves that
144
+ // view, because that is what the responder system expects.
145
+ facebook::react::Tag activeTarget_{0};
146
+ bool isDown_{false};
147
+
148
+ // Where the cursor was, so that enter and leave can be told from over and
149
+ // out. See core/HoverTracker.h.
150
+ HoverTracker hover_;
151
+ };
152
+
153
+ } // namespace basalt
@@ -0,0 +1,27 @@
1
+ // The UI thread, as a thing that can be installed and asked about.
2
+ //
3
+ // `core/PlatformServices.h` declares `postToUiThread` and `isUiThread`, which
4
+ // is all the shared half needs. Windows needs two more facts that are not
5
+ // portable enough to belong there: something has to *create* the message-only
6
+ // window the posting goes through, and callers occasionally have to know
7
+ // whether one exists at all.
8
+ //
9
+ // The second is not a nicety. With no window installed `postToUiThread` runs
10
+ // its work inline, which is correct for the harness and the tests -- there is
11
+ // one thread and no loop to post to -- and quietly wrong for work posted from a
12
+ // worker, because "inline" then means "on the worker". Anything that would
13
+ // otherwise hand cross-thread work to a loop that does not exist should ask
14
+ // first and stay on one thread instead.
15
+
16
+ #pragma once
17
+
18
+ namespace basalt {
19
+
20
+ // Called by the host from the thread that owns its message loop, before React
21
+ // Native starts. Idempotent.
22
+ void installUiThread();
23
+
24
+ // Whether `postToUiThread` will actually marshal, rather than running inline.
25
+ bool hasUiThread();
26
+
27
+ } // namespace basalt
@@ -0,0 +1,329 @@
1
+ // The Windows half of core/WindowControl.h.
2
+ //
3
+ // The only one of the three where the coordinates already agree with React
4
+ // Native's -- top-left origin, y growing downwards -- and the only one where
5
+ // every function can be answered honestly.
6
+ //
7
+ // What it does have to be careful about is the difference between a window's
8
+ // frame and its client area. `GetWindowRect` is the frame, including the border
9
+ // and the title bar; `setWindowSize` is given the size an app wants, and an app
10
+ // asking for 900x700 means its own content. So the size is adjusted outwards by
11
+ // whatever the frame costs, which is what `AdjustWindowRectEx` is for -- and
12
+ // which a window with a hidden title bar costs nothing for, so the same call
13
+ // answers both cases.
14
+
15
+ #include <vector>
16
+ #include "WindowControl.h"
17
+
18
+ #include <windows.h>
19
+
20
+ // After windows.h, which it depends on: GetDpiForMonitor, for a per-display
21
+ // scale factor. GetDpiForWindow would answer for the window's monitor only.
22
+ #include <shellscalingapi.h>
23
+
24
+ namespace basalt {
25
+
26
+ namespace {
27
+
28
+
29
+ HWND window() {
30
+ HWND active = GetActiveWindow();
31
+ return active != nullptr ? active : GetForegroundWindow();
32
+ }
33
+
34
+ } // namespace
35
+
36
+ // Every monitor, through the callback API that is the only way to ask.
37
+ //
38
+ // `MONITORINFOEXW` carries both rectangles -- `rcMonitor` is the glass and
39
+ // `rcWork` is what the taskbar leaves -- which is why the work area needs no
40
+ // second call here and cannot be had at all on GTK.
41
+ //
42
+ // The coordinates are already what this platform means by desktop
43
+ // coordinates: Windows anchors them at the primary monitor's top-left and
44
+ // grows down, which is the convention the other two are converted into.
45
+ std::vector<DisplayInfo> displays() {
46
+ std::vector<DisplayInfo> found;
47
+ EnumDisplayMonitors(
48
+ nullptr,
49
+ nullptr,
50
+ [](HMONITOR monitor, HDC, LPRECT, LPARAM data) -> BOOL {
51
+ auto *out = reinterpret_cast<std::vector<DisplayInfo> *>(data);
52
+ MONITORINFOEXW info{};
53
+ info.cbSize = sizeof(info);
54
+ if (!GetMonitorInfoW(monitor, &info)) {
55
+ return TRUE;
56
+ }
57
+
58
+ DisplayInfo entry;
59
+ entry.x = info.rcMonitor.left;
60
+ entry.y = info.rcMonitor.top;
61
+ entry.width = info.rcMonitor.right - info.rcMonitor.left;
62
+ entry.height = info.rcMonitor.bottom - info.rcMonitor.top;
63
+ entry.workX = info.rcWork.left;
64
+ entry.workY = info.rcWork.top;
65
+ entry.workWidth = info.rcWork.right - info.rcWork.left;
66
+ entry.workHeight = info.rcWork.bottom - info.rcWork.top;
67
+
68
+ // 96 DPI is 100%, which is what makes this a ratio rather than a
69
+ // number of dots. A display at 150% answers 144 and reports 1.5.
70
+ UINT dpiX = 96;
71
+ UINT dpiY = 96;
72
+ if (SUCCEEDED(GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY)) && dpiX > 0) {
73
+ entry.scaleFactor = static_cast<double>(dpiX) / 96.0;
74
+ }
75
+ entry.primary = (info.dwFlags & MONITORINFOF_PRIMARY) != 0;
76
+
77
+ // Primary first, to match the order the other hosts report and the
78
+ // order the header promises. EnumDisplayMonitors does not guarantee
79
+ // one.
80
+ if (entry.primary) {
81
+ out->insert(out->begin(), entry);
82
+ } else {
83
+ out->push_back(entry);
84
+ }
85
+ return TRUE;
86
+ },
87
+ reinterpret_cast<LPARAM>(&found));
88
+ return found;
89
+ }
90
+
91
+ PointerPosition pointerPosition() {
92
+ PointerPosition where;
93
+ POINT point{};
94
+ if (GetCursorPos(&point)) {
95
+ where.x = point.x;
96
+ where.y = point.y;
97
+ where.known = true;
98
+ }
99
+ return where;
100
+ }
101
+
102
+ WindowBounds windowBounds() {
103
+ WindowBounds bounds;
104
+ HWND target = window();
105
+ if (target == nullptr) {
106
+ return bounds;
107
+ }
108
+
109
+ RECT frame{};
110
+ if (GetWindowRect(target, &frame)) {
111
+ bounds.x = frame.left;
112
+ bounds.y = frame.top;
113
+ }
114
+ // The client area, which is what an app laid out and what the other two
115
+ // hosts report: a frame's size includes a border the app never drew in.
116
+ RECT client{};
117
+ if (GetClientRect(target, &client)) {
118
+ bounds.width = client.right - client.left;
119
+ bounds.height = client.bottom - client.top;
120
+ }
121
+
122
+ bounds.maximized = IsZoomed(target) != FALSE;
123
+
124
+ // Full screen is not a state Windows has; it is a window with no border
125
+ // filling a monitor, which is how every application does it. So the question
126
+ // is asked the way the answer is made: no title bar, and exactly the size of
127
+ // the monitor it is on.
128
+ if (const HMONITOR monitor = MonitorFromWindow(target, MONITOR_DEFAULTTONEAREST)) {
129
+ MONITORINFO info{};
130
+ info.cbSize = sizeof(info);
131
+ if (GetMonitorInfoW(monitor, &info)) {
132
+ const LONG style = GetWindowLongW(target, GWL_STYLE);
133
+ bounds.fullScreen = (style & WS_OVERLAPPEDWINDOW) == 0 &&
134
+ frame.left == info.rcMonitor.left && frame.top == info.rcMonitor.top &&
135
+ frame.right == info.rcMonitor.right && frame.bottom == info.rcMonitor.bottom;
136
+ }
137
+ }
138
+ return bounds;
139
+ }
140
+
141
+ void setWindowSize(double width, double height) {
142
+ HWND target = window();
143
+ if (target == nullptr || width <= 0.0 || height <= 0.0) {
144
+ return;
145
+ }
146
+ // Outwards from the client size the app asked for to the frame size Windows
147
+ // positions. A borderless window adjusts by nothing, so this is right for
148
+ // both title-bar styles.
149
+ RECT desired{0, 0, static_cast<LONG>(width), static_cast<LONG>(height)};
150
+ const LONG style = GetWindowLongW(target, GWL_STYLE);
151
+ const LONG exStyle = GetWindowLongW(target, GWL_EXSTYLE);
152
+ AdjustWindowRectEx(&desired, static_cast<DWORD>(style), FALSE, static_cast<DWORD>(exStyle));
153
+
154
+ SetWindowPos(target,
155
+ nullptr,
156
+ 0,
157
+ 0,
158
+ desired.right - desired.left,
159
+ desired.bottom - desired.top,
160
+ SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
161
+ }
162
+
163
+ void setWindowPosition(double x, double y) {
164
+ HWND target = window();
165
+ if (target == nullptr) {
166
+ return;
167
+ }
168
+ SetWindowPos(target,
169
+ nullptr,
170
+ static_cast<int>(x),
171
+ static_cast<int>(y),
172
+ 0,
173
+ 0,
174
+ SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
175
+ }
176
+
177
+ void centerWindow() {
178
+ HWND target = window();
179
+ if (target == nullptr) {
180
+ return;
181
+ }
182
+ RECT frame{};
183
+ if (!GetWindowRect(target, &frame)) {
184
+ return;
185
+ }
186
+ // The work area of the monitor the window is on, not the primary one and not
187
+ // the whole monitor: centring into the space the taskbar leaves is what a
188
+ // person means, and what every other application does.
189
+ const HMONITOR monitor = MonitorFromWindow(target, MONITOR_DEFAULTTONEAREST);
190
+ MONITORINFO info{};
191
+ info.cbSize = sizeof(info);
192
+ if (monitor == nullptr || !GetMonitorInfoW(monitor, &info)) {
193
+ return;
194
+ }
195
+
196
+ const LONG width = frame.right - frame.left;
197
+ const LONG height = frame.bottom - frame.top;
198
+ const LONG x = info.rcWork.left + ((info.rcWork.right - info.rcWork.left) - width) / 2;
199
+ const LONG y = info.rcWork.top + ((info.rcWork.bottom - info.rcWork.top) - height) / 2;
200
+ SetWindowPos(target, nullptr, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
201
+ }
202
+
203
+ void setWindowFullScreen(bool fullScreen) {
204
+ HWND target = window();
205
+ if (target == nullptr) {
206
+ return;
207
+ }
208
+
209
+ // Where the window was before it filled the screen, so that leaving puts it
210
+ // back. One window, so one saved rect; see core/WindowControl.h on that
211
+ // limit.
212
+ static RECT restore{};
213
+ static LONG restoreStyle = 0;
214
+
215
+ const LONG style = GetWindowLongW(target, GWL_STYLE);
216
+ const bool already = (style & WS_OVERLAPPEDWINDOW) == 0;
217
+ if (already == fullScreen) {
218
+ return;
219
+ }
220
+
221
+ if (fullScreen) {
222
+ GetWindowRect(target, &restore);
223
+ restoreStyle = style;
224
+
225
+ const HMONITOR monitor = MonitorFromWindow(target, MONITOR_DEFAULTTONEAREST);
226
+ MONITORINFO info{};
227
+ info.cbSize = sizeof(info);
228
+ if (monitor == nullptr || !GetMonitorInfoW(monitor, &info)) {
229
+ return;
230
+ }
231
+ SetWindowLongW(target, GWL_STYLE, style & ~WS_OVERLAPPEDWINDOW);
232
+ // The whole monitor rather than its work area: filling the screen means
233
+ // over the taskbar, which is the difference between this and maximising.
234
+ SetWindowPos(target,
235
+ HWND_TOP,
236
+ info.rcMonitor.left,
237
+ info.rcMonitor.top,
238
+ info.rcMonitor.right - info.rcMonitor.left,
239
+ info.rcMonitor.bottom - info.rcMonitor.top,
240
+ SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
241
+ return;
242
+ }
243
+
244
+ SetWindowLongW(target, GWL_STYLE, restoreStyle != 0 ? restoreStyle : (style | WS_OVERLAPPEDWINDOW));
245
+ SetWindowPos(target,
246
+ nullptr,
247
+ restore.left,
248
+ restore.top,
249
+ restore.right - restore.left,
250
+ restore.bottom - restore.top,
251
+ SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
252
+ }
253
+
254
+ void applyWindowSizeLimits() {
255
+ HWND target = window();
256
+ if (target == nullptr) {
257
+ return;
258
+ }
259
+ // Nothing to set. Windows asks rather than being told: WM_GETMINMAXINFO
260
+ // arrives whenever the window manager wants the answer, and the host replies
261
+ // from `windowSizeLimits()`. All this does is make it ask again, so that a
262
+ // limit set while the window is already too big takes effect now rather than
263
+ // the next time somebody drags a corner.
264
+ RECT frame{};
265
+ if (!GetWindowRect(target, &frame)) {
266
+ return;
267
+ }
268
+ SetWindowPos(target,
269
+ nullptr,
270
+ frame.left,
271
+ frame.top,
272
+ frame.right - frame.left,
273
+ frame.bottom - frame.top,
274
+ SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
275
+ }
276
+
277
+ void setWindowResizable(bool resizable) {
278
+ HWND target = window();
279
+ if (target == nullptr) {
280
+ return;
281
+ }
282
+ LONG style = GetWindowLongW(target, GWL_STYLE);
283
+ // The sizing border and the maximise button together. Leaving the button on a
284
+ // window that cannot be sized would be an enabled control that does nothing,
285
+ // which is the same mistake as a greyed-out one that works.
286
+ if (resizable) {
287
+ style |= (WS_THICKFRAME | WS_MAXIMIZEBOX);
288
+ } else {
289
+ style &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
290
+ }
291
+ SetWindowLongW(target, GWL_STYLE, style);
292
+ // SWP_FRAMECHANGED, or the border is recalculated only the next time
293
+ // something else moves the window -- so the style changes and the frame does
294
+ // not, until it suddenly does.
295
+ SetWindowPos(target,
296
+ nullptr,
297
+ 0,
298
+ 0,
299
+ 0,
300
+ 0,
301
+ SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE |
302
+ SWP_FRAMECHANGED);
303
+ }
304
+
305
+ void setWindowAlwaysOnTop(bool alwaysOnTop) {
306
+ HWND target = window();
307
+ if (target == nullptr) {
308
+ return;
309
+ }
310
+ SetWindowPos(target,
311
+ alwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST,
312
+ 0,
313
+ 0,
314
+ 0,
315
+ 0,
316
+ SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
317
+ }
318
+
319
+ WindowCapabilities windowCapabilities() {
320
+ WindowCapabilities capabilities;
321
+ capabilities.position = true;
322
+ capabilities.minimumSize = true;
323
+ capabilities.maximumSize = true;
324
+ capabilities.resizable = true;
325
+ capabilities.alwaysOnTop = true;
326
+ return capabilities;
327
+ }
328
+
329
+ } // namespace basalt