basalt-appkit 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/runMacos.js +33 -0
- package/cli/shared.js +75 -0
- package/native/CMakeLists.txt +298 -0
- package/native/appkit/AppKitAnimationChoreographer.h +54 -0
- package/native/appkit/AppKitAnimationChoreographer.mm +125 -0
- package/native/appkit/AppKitDropTarget.h +34 -0
- package/native/appkit/AppKitDropTarget.mm +292 -0
- package/native/appkit/AppKitFocus.h +120 -0
- package/native/appkit/AppKitFocus.mm +293 -0
- package/native/appkit/AppKitImageLoader.h +80 -0
- package/native/appkit/AppKitImageLoader.mm +156 -0
- package/native/appkit/AppKitKeyEvents.h +26 -0
- package/native/appkit/AppKitKeyEvents.mm +97 -0
- package/native/appkit/AppKitMenuBar.mm +424 -0
- package/native/appkit/AppKitMountingManager.h +135 -0
- package/native/appkit/AppKitMountingManager.mm +946 -0
- package/native/appkit/AppKitRunLoopObserver.h +34 -0
- package/native/appkit/AppKitRunLoopObserver.mm +42 -0
- package/native/appkit/AppKitScrollView.h +168 -0
- package/native/appkit/AppKitScrollView.mm +550 -0
- package/native/appkit/AppKitSkiaContext.h +33 -0
- package/native/appkit/AppKitSkiaContext.mm +255 -0
- package/native/appkit/AppKitSkiaModule.h +53 -0
- package/native/appkit/AppKitSkiaModule.mm +81 -0
- package/native/appkit/AppKitSkiaPeer.h +37 -0
- package/native/appkit/AppKitSkiaPeer.mm +123 -0
- package/native/appkit/AppKitSkiaView.h +62 -0
- package/native/appkit/AppKitSnapshot.h +21 -0
- package/native/appkit/AppKitSnapshot.mm +91 -0
- package/native/appkit/AppKitTextInput.h +140 -0
- package/native/appkit/AppKitTextInput.mm +588 -0
- package/native/appkit/AppKitTextPeer.h +74 -0
- package/native/appkit/AppKitTextPeer.mm +353 -0
- package/native/appkit/AppKitTitleBar.h +90 -0
- package/native/appkit/AppKitTitleBar.mm +186 -0
- package/native/appkit/AppKitTouchDispatcher.h +138 -0
- package/native/appkit/AppKitTouchDispatcher.mm +514 -0
- package/native/appkit/AppKitWindowControl.mm +222 -0
- package/native/appkit/AppKitWindowModule.h +62 -0
- package/native/appkit/AppKitWindowModule.mm +194 -0
- package/native/appkit/ColorSchemeAppKit.mm +71 -0
- package/native/appkit/ComponentRegistryAppKit.mm +121 -0
- package/native/appkit/CoreTextLayout.h +43 -0
- package/native/appkit/CoreTextLayout.mm +252 -0
- package/native/appkit/CoreTextLayoutManager.mm +138 -0
- package/native/appkit/FontRegistryCoreText.mm +148 -0
- package/native/appkit/PlatformServicesAppKit.mm +540 -0
- package/native/appkit/RnAppKitView.h +417 -0
- package/native/appkit/RnAppKitView.mm +1605 -0
- package/native/appkit/RnTextLayout.h +38 -0
- package/native/appkit/RnTextLayout.mm +168 -0
- package/native/appkit/demo_layout_appkit.mm +93 -0
- package/native/appkit/main_appkit.mm +1860 -0
- package/native/appkit/mount_harness_appkit.mm +197 -0
- package/native/tests/test_appkit_accessibility.mm +170 -0
- package/native/tests/test_appkit_image.mm +237 -0
- package/native/tests/test_appkit_input.mm +852 -0
- package/native/tests/test_appkit_key_events.mm +150 -0
- package/native/tests/test_appkit_mounting.mm +430 -0
- package/native/tests/test_appkit_scrollbar.mm +132 -0
- package/native/tests/test_appkit_services.mm +57 -0
- package/native/tests/test_appkit_skia.mm +212 -0
- package/native/tests/test_appkit_text.mm +180 -0
- package/native/tests/test_appkit_textinput.mm +748 -0
- package/native/tests/test_appkit_titlebar.mm +107 -0
- package/native/tests/test_appkit_view.mm +237 -0
- package/package.json +26 -0
- package/react-native.config.js +22 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
// The macOS half of core/WindowControl.h.
|
|
2
|
+
//
|
|
3
|
+
// The one place this differs from the other two in more than spelling is the
|
|
4
|
+
// coordinate system. AppKit's origin is the bottom left of the *primary*
|
|
5
|
+
// display and y grows upwards; every other desktop, and React Native, put the
|
|
6
|
+
// origin at the top left and grow downwards. So `windowBounds` and
|
|
7
|
+
// `setWindowPosition` flip, against the primary screen's height, and an app
|
|
8
|
+
// that remembers a position and restores it gets the same window back.
|
|
9
|
+
//
|
|
10
|
+
// Not flipping would be the subtler bug: a window would be restored the same
|
|
11
|
+
// distance from the wrong edge, which looks almost right on a large display and
|
|
12
|
+
// entirely wrong on a small one.
|
|
13
|
+
|
|
14
|
+
#include <vector>
|
|
15
|
+
#include "WindowControl.h"
|
|
16
|
+
|
|
17
|
+
#import <Cocoa/Cocoa.h>
|
|
18
|
+
|
|
19
|
+
namespace basalt {
|
|
20
|
+
|
|
21
|
+
namespace {
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
NSWindow *window() {
|
|
25
|
+
return NSApp.keyWindow != nil ? NSApp.keyWindow : NSApp.mainWindow;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// The height AppKit measures y from. `NSScreen.screens.firstObject` is the
|
|
29
|
+
// primary display, which is the one AppKit's global coordinates are anchored
|
|
30
|
+
// to -- not `mainScreen`, which is wherever the key window happens to be.
|
|
31
|
+
CGFloat primaryHeight() {
|
|
32
|
+
NSScreen *primary = NSScreen.screens.firstObject;
|
|
33
|
+
return primary != nil ? NSMaxY(primary.frame) : 0.0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
} // namespace
|
|
37
|
+
|
|
38
|
+
std::vector<DisplayInfo> displays() {
|
|
39
|
+
std::vector<DisplayInfo> found;
|
|
40
|
+
@autoreleasepool {
|
|
41
|
+
const CGFloat anchor = primaryHeight();
|
|
42
|
+
for (NSScreen *screen in NSScreen.screens) {
|
|
43
|
+
DisplayInfo info;
|
|
44
|
+
const NSRect frame = screen.frame;
|
|
45
|
+
const NSRect visible = screen.visibleFrame;
|
|
46
|
+
|
|
47
|
+
info.x = frame.origin.x;
|
|
48
|
+
// The same flip windowBounds does, and it has to be the same one: a
|
|
49
|
+
// display and a window are compared by apps that place windows, and two
|
|
50
|
+
// conventions in one API would be a bug nobody could see until a second
|
|
51
|
+
// monitor was above the first.
|
|
52
|
+
info.y = anchor - NSMaxY(frame);
|
|
53
|
+
info.width = frame.size.width;
|
|
54
|
+
info.height = frame.size.height;
|
|
55
|
+
|
|
56
|
+
// `visibleFrame` is what is left after the menu bar and the Dock, which
|
|
57
|
+
// is exactly what the work area means.
|
|
58
|
+
info.workX = visible.origin.x;
|
|
59
|
+
info.workY = anchor - NSMaxY(visible);
|
|
60
|
+
info.workWidth = visible.size.width;
|
|
61
|
+
info.workHeight = visible.size.height;
|
|
62
|
+
|
|
63
|
+
info.scaleFactor = screen.backingScaleFactor > 0.0 ? screen.backingScaleFactor : 1.0;
|
|
64
|
+
// `NSScreen.screens` is documented to put the primary first, and that is
|
|
65
|
+
// also the screen the coordinates above are anchored to -- so this is
|
|
66
|
+
// the same answer primaryHeight() relies on rather than a second one.
|
|
67
|
+
info.primary = found.empty();
|
|
68
|
+
found.push_back(info);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return found;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
PointerPosition pointerPosition() {
|
|
75
|
+
PointerPosition where;
|
|
76
|
+
@autoreleasepool {
|
|
77
|
+
// Global, and available whether or not the pointer is over this app --
|
|
78
|
+
// which is the whole point of asking, and is not true on every desktop.
|
|
79
|
+
const NSPoint mouse = NSEvent.mouseLocation;
|
|
80
|
+
where.x = mouse.x;
|
|
81
|
+
where.y = primaryHeight() - mouse.y;
|
|
82
|
+
where.known = true;
|
|
83
|
+
}
|
|
84
|
+
return where;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
WindowBounds windowBounds() {
|
|
88
|
+
WindowBounds bounds;
|
|
89
|
+
@autoreleasepool {
|
|
90
|
+
NSWindow *target = window();
|
|
91
|
+
if (target == nil) {
|
|
92
|
+
return bounds;
|
|
93
|
+
}
|
|
94
|
+
const NSRect frame = target.frame;
|
|
95
|
+
bounds.x = frame.origin.x;
|
|
96
|
+
// Top-left origin, growing downwards. See the header.
|
|
97
|
+
bounds.y = primaryHeight() - NSMaxY(frame);
|
|
98
|
+
bounds.width = frame.size.width;
|
|
99
|
+
bounds.height = frame.size.height;
|
|
100
|
+
bounds.fullScreen = (target.styleMask & NSWindowStyleMaskFullScreen) != 0;
|
|
101
|
+
bounds.maximized = target.isZoomed;
|
|
102
|
+
}
|
|
103
|
+
return bounds;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
void setWindowSize(double width, double height) {
|
|
107
|
+
if (width <= 0.0 || height <= 0.0) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
@autoreleasepool {
|
|
111
|
+
NSWindow *target = window();
|
|
112
|
+
if (target == nil) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
NSRect frame = target.frame;
|
|
116
|
+
// The top edge stays where it is. Resizing a window from its bottom-left
|
|
117
|
+
// origin -- which is what setting the size alone would do -- moves its
|
|
118
|
+
// title bar, and every other desktop grows a window downwards.
|
|
119
|
+
frame.origin.y = NSMaxY(frame) - height;
|
|
120
|
+
frame.size = NSMakeSize(width, height);
|
|
121
|
+
[target setFrame:frame display:YES animate:NO];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
void setWindowPosition(double x, double y) {
|
|
126
|
+
@autoreleasepool {
|
|
127
|
+
NSWindow *target = window();
|
|
128
|
+
if (target == nil) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
// `setFrameTopLeftPoint` takes AppKit coordinates, so the y this was given
|
|
132
|
+
// -- measured down from the top -- is measured back up from the bottom.
|
|
133
|
+
[target setFrameTopLeftPoint:NSMakePoint(x, primaryHeight() - y)];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
void centerWindow() {
|
|
138
|
+
@autoreleasepool {
|
|
139
|
+
// AppKit's own, which puts the window slightly above centre: that is where
|
|
140
|
+
// Apple's guidelines say a window goes, and an app asking to be centred on
|
|
141
|
+
// a Mac is asking for that rather than for arithmetic.
|
|
142
|
+
[window() center];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
void setWindowFullScreen(bool fullScreen) {
|
|
147
|
+
@autoreleasepool {
|
|
148
|
+
NSWindow *target = window();
|
|
149
|
+
if (target == nil) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const bool already = (target.styleMask & NSWindowStyleMaskFullScreen) != 0;
|
|
153
|
+
// `toggleFullScreen:` is the only way in, and it toggles -- so asking for
|
|
154
|
+
// the state it is already in has to do nothing rather than reverse it.
|
|
155
|
+
if (already != fullScreen) {
|
|
156
|
+
[target toggleFullScreen:nil];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
void applyWindowSizeLimits() {
|
|
162
|
+
@autoreleasepool {
|
|
163
|
+
NSWindow *target = window();
|
|
164
|
+
if (target == nil) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const WindowSizeLimits limits = windowSizeLimits();
|
|
168
|
+
// `contentMinSize` and `contentMaxSize` rather than `minSize`/`maxSize`:
|
|
169
|
+
// those are the frame, which includes the title bar, and an app asking for
|
|
170
|
+
// a minimum of 400x300 means 400x300 of its own content -- the same
|
|
171
|
+
// distinction `setWindowSize` already makes.
|
|
172
|
+
target.contentMinSize = NSMakeSize(limits.minWidth > 0.0 ? limits.minWidth : 0.0,
|
|
173
|
+
limits.minHeight > 0.0 ? limits.minHeight : 0.0);
|
|
174
|
+
// No maximum is CGFLOAT_MAX rather than zero, which would be a window that
|
|
175
|
+
// cannot be any size at all.
|
|
176
|
+
target.contentMaxSize = NSMakeSize(limits.maxWidth > 0.0 ? limits.maxWidth : CGFLOAT_MAX,
|
|
177
|
+
limits.maxHeight > 0.0 ? limits.maxHeight : CGFLOAT_MAX);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
void setWindowResizable(bool resizable) {
|
|
182
|
+
@autoreleasepool {
|
|
183
|
+
NSWindow *target = window();
|
|
184
|
+
if (target == nil) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
// A bit in the style mask, which is also what draws the zoom button: a
|
|
188
|
+
// window that cannot be resized should not offer to be.
|
|
189
|
+
if (resizable) {
|
|
190
|
+
target.styleMask |= NSWindowStyleMaskResizable;
|
|
191
|
+
} else {
|
|
192
|
+
target.styleMask &= ~NSWindowStyleMaskResizable;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
void setWindowAlwaysOnTop(bool alwaysOnTop) {
|
|
198
|
+
@autoreleasepool {
|
|
199
|
+
NSWindow *target = window();
|
|
200
|
+
if (target == nil) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
// Floating, not one of the higher levels. `NSFloatingWindowLevel` puts a
|
|
204
|
+
// window above other applications and still below the things that are
|
|
205
|
+
// supposed to outrank an application -- a screen lock, a system alert -- and
|
|
206
|
+
// asking for more than that is asking to be the bug report.
|
|
207
|
+
target.level = alwaysOnTop ? NSFloatingWindowLevel : NSNormalWindowLevel;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
WindowCapabilities windowCapabilities() {
|
|
212
|
+
// The one desktop where every one of these is real.
|
|
213
|
+
WindowCapabilities capabilities;
|
|
214
|
+
capabilities.position = true;
|
|
215
|
+
capabilities.minimumSize = true;
|
|
216
|
+
capabilities.maximumSize = true;
|
|
217
|
+
capabilities.resizable = true;
|
|
218
|
+
capabilities.alwaysOnTop = true;
|
|
219
|
+
return capabilities;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
} // namespace basalt
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// `BasaltWindow` on macOS: the module src/TitleBar.js talks to.
|
|
2
|
+
//
|
|
3
|
+
// The same four methods Win32WindowModule has, because the JavaScript is
|
|
4
|
+
// shared and does not know which desktop it is on. What differs is underneath,
|
|
5
|
+
// in AppKitTitleBar.h.
|
|
6
|
+
|
|
7
|
+
#pragma once
|
|
8
|
+
|
|
9
|
+
#include <ReactCommon/TurboModule.h>
|
|
10
|
+
|
|
11
|
+
#include <memory>
|
|
12
|
+
|
|
13
|
+
namespace basalt {
|
|
14
|
+
|
|
15
|
+
class AppKitWindowModule : public facebook::react::TurboModule {
|
|
16
|
+
public:
|
|
17
|
+
static constexpr const char *kModuleName = "BasaltWindow";
|
|
18
|
+
|
|
19
|
+
explicit AppKitWindowModule(std::shared_ptr<facebook::react::CallInvoker> jsInvoker);
|
|
20
|
+
~AppKitWindowModule() override;
|
|
21
|
+
|
|
22
|
+
private:
|
|
23
|
+
static facebook::jsi::Value setTitle(facebook::jsi::Runtime &runtime,
|
|
24
|
+
facebook::react::TurboModule &module,
|
|
25
|
+
const facebook::jsi::Value *args,
|
|
26
|
+
size_t count);
|
|
27
|
+
static facebook::jsi::Value setTitleBarColors(facebook::jsi::Runtime &runtime,
|
|
28
|
+
facebook::react::TurboModule &module,
|
|
29
|
+
const facebook::jsi::Value *args,
|
|
30
|
+
size_t count);
|
|
31
|
+
static facebook::jsi::Value setTitleBarStyle(facebook::jsi::Runtime &runtime,
|
|
32
|
+
facebook::react::TurboModule &module,
|
|
33
|
+
const facebook::jsi::Value *args,
|
|
34
|
+
size_t count);
|
|
35
|
+
static facebook::jsi::Value getTitleBarMetrics(facebook::jsi::Runtime &runtime,
|
|
36
|
+
facebook::react::TurboModule &module,
|
|
37
|
+
const facebook::jsi::Value *args,
|
|
38
|
+
size_t count);
|
|
39
|
+
// The caption buttons' functions, for an app drawing its own header.
|
|
40
|
+
static facebook::jsi::Value minimize(facebook::jsi::Runtime &runtime,
|
|
41
|
+
facebook::react::TurboModule &module,
|
|
42
|
+
const facebook::jsi::Value *args,
|
|
43
|
+
size_t count);
|
|
44
|
+
static facebook::jsi::Value toggleMaximize(facebook::jsi::Runtime &runtime,
|
|
45
|
+
facebook::react::TurboModule &module,
|
|
46
|
+
const facebook::jsi::Value *args,
|
|
47
|
+
size_t count);
|
|
48
|
+
static facebook::jsi::Value closeWindow(facebook::jsi::Runtime &runtime,
|
|
49
|
+
facebook::react::TurboModule &module,
|
|
50
|
+
const facebook::jsi::Value *args,
|
|
51
|
+
size_t count);
|
|
52
|
+
static facebook::jsi::Value startWindowDrag(facebook::jsi::Runtime &runtime,
|
|
53
|
+
facebook::react::TurboModule &module,
|
|
54
|
+
const facebook::jsi::Value *args,
|
|
55
|
+
size_t count);
|
|
56
|
+
static facebook::jsi::Value noop(facebook::jsi::Runtime &runtime,
|
|
57
|
+
facebook::react::TurboModule &module,
|
|
58
|
+
const facebook::jsi::Value *args,
|
|
59
|
+
size_t count);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
} // namespace basalt
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#import "AppKitWindowModule.h"
|
|
2
|
+
|
|
3
|
+
#import "AppKitTitleBar.h"
|
|
4
|
+
#include "PlatformServices.h"
|
|
5
|
+
|
|
6
|
+
#include "WindowControl.h"
|
|
7
|
+
#include "WindowMethods.h"
|
|
8
|
+
#include <cstdint>
|
|
9
|
+
#include <optional>
|
|
10
|
+
#include <string>
|
|
11
|
+
#include <utility>
|
|
12
|
+
#include <vector>
|
|
13
|
+
|
|
14
|
+
namespace basalt {
|
|
15
|
+
|
|
16
|
+
namespace {
|
|
17
|
+
|
|
18
|
+
using facebook::jsi::Object;
|
|
19
|
+
using facebook::jsi::Runtime;
|
|
20
|
+
using facebook::jsi::String;
|
|
21
|
+
using facebook::jsi::Value;
|
|
22
|
+
using facebook::react::TurboModule;
|
|
23
|
+
|
|
24
|
+
constexpr const char *kMetricsChangedEvent = "basaltTitleBarMetricsChanged";
|
|
25
|
+
|
|
26
|
+
// processColor's 0xAARRGGBB, or nullopt for anything that is not a number --
|
|
27
|
+
// which is how JavaScript asks for the system's colour back.
|
|
28
|
+
std::optional<uint32_t> colorArgument(const Value *args, size_t count, size_t index) {
|
|
29
|
+
if (index >= count || !args[index].isNumber()) {
|
|
30
|
+
return std::nullopt;
|
|
31
|
+
}
|
|
32
|
+
return static_cast<uint32_t>(static_cast<int64_t>(args[index].asNumber()));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
Value metricsValue(Runtime &runtime, const TitleBarMetrics &metrics) {
|
|
36
|
+
Object object(runtime);
|
|
37
|
+
object.setProperty(runtime, "height", metrics.height);
|
|
38
|
+
object.setProperty(runtime, "buttonsWidth", metrics.buttonsWidth);
|
|
39
|
+
object.setProperty(runtime,
|
|
40
|
+
"style",
|
|
41
|
+
String::createFromAscii(
|
|
42
|
+
runtime, metrics.style == TitleBarStyle::Hidden ? "hidden" : "native"));
|
|
43
|
+
return Value(runtime, object);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
} // namespace
|
|
47
|
+
|
|
48
|
+
AppKitWindowModule::AppKitWindowModule(std::shared_ptr<facebook::react::CallInvoker> jsInvoker)
|
|
49
|
+
: TurboModule(kModuleName, std::move(jsInvoker)) {
|
|
50
|
+
methodMap_["setTitle"] = MethodMetadata{1, setTitle};
|
|
51
|
+
methodMap_["setTitleBarColors"] = MethodMetadata{3, setTitleBarColors};
|
|
52
|
+
methodMap_["setTitleBarStyle"] = MethodMetadata{1, setTitleBarStyle};
|
|
53
|
+
methodMap_["getTitleBarMetrics"] = MethodMetadata{0, getTitleBarMetrics};
|
|
54
|
+
methodMap_["minimize"] = MethodMetadata{0, minimize};
|
|
55
|
+
methodMap_["toggleMaximize"] = MethodMetadata{0, toggleMaximize};
|
|
56
|
+
methodMap_["close"] = MethodMetadata{0, closeWindow};
|
|
57
|
+
methodMap_["startWindowDrag"] = MethodMetadata{0, startWindowDrag};
|
|
58
|
+
// A window's geometry. The bodies are in core/WindowMethods.h, written once:
|
|
59
|
+
// nothing about reading two numbers or shaping the answer differs per
|
|
60
|
+
// desktop, and three copies would be three chances to drift.
|
|
61
|
+
methodMap_["setSize"] = MethodMetadata{2, windowSetSize};
|
|
62
|
+
methodMap_["setPosition"] = MethodMetadata{2, windowSetPosition};
|
|
63
|
+
methodMap_["center"] = MethodMetadata{0, windowCenter};
|
|
64
|
+
methodMap_["setFullScreen"] = MethodMetadata{1, windowSetFullScreen};
|
|
65
|
+
// How big it may be, whether it may be resized at all, and whether it floats.
|
|
66
|
+
// Not every desktop does every one of these; `getCapabilities` is how an app
|
|
67
|
+
// finds out rather than guessing. See core/WindowControl.h.
|
|
68
|
+
methodMap_["setMinimumSize"] = MethodMetadata{2, windowSetMinimumSize};
|
|
69
|
+
methodMap_["setMaximumSize"] = MethodMetadata{2, windowSetMaximumSize};
|
|
70
|
+
methodMap_["setResizable"] = MethodMetadata{1, windowSetResizable};
|
|
71
|
+
methodMap_["setAlwaysOnTop"] = MethodMetadata{1, windowSetAlwaysOnTop};
|
|
72
|
+
methodMap_["getCapabilities"] = MethodMetadata{0, windowGetCapabilities};
|
|
73
|
+
methodMap_["getBounds"] = MethodMetadata{0, windowGetBounds};
|
|
74
|
+
// What a NativeEventEmitter over this module calls; the events go out as
|
|
75
|
+
// device events either way.
|
|
76
|
+
methodMap_["addListener"] = MethodMetadata{1, noop};
|
|
77
|
+
methodMap_["removeListeners"] = MethodMetadata{1, noop};
|
|
78
|
+
|
|
79
|
+
titleBar().setMetricsListener([this](const TitleBarMetrics &metrics) {
|
|
80
|
+
emitDeviceEvent(kMetricsChangedEvent, [metrics](Runtime &runtime, std::vector<Value> &args) {
|
|
81
|
+
args.emplace_back(metricsValue(runtime, metrics));
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// The window's geometry, the same way the title bar's metrics go out: a
|
|
86
|
+
// device event, so `useWindow()` can hand an app live bounds rather than a
|
|
87
|
+
// snapshot it has to remember to refresh.
|
|
88
|
+
setWindowBoundsListener([this](const WindowBounds &bounds) {
|
|
89
|
+
emitDeviceEvent(kWindowBoundsEvent, [bounds](Runtime &runtime, std::vector<Value> &args) {
|
|
90
|
+
Object object(runtime);
|
|
91
|
+
object.setProperty(runtime, "x", bounds.x);
|
|
92
|
+
object.setProperty(runtime, "y", bounds.y);
|
|
93
|
+
object.setProperty(runtime, "width", bounds.width);
|
|
94
|
+
object.setProperty(runtime, "height", bounds.height);
|
|
95
|
+
object.setProperty(runtime, "fullScreen", bounds.fullScreen);
|
|
96
|
+
object.setProperty(runtime, "maximized", bounds.maximized);
|
|
97
|
+
args.emplace_back(Value(runtime, object));
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// And ask for the bounds once, now that something is listening. The host
|
|
102
|
+
// primes the cache when its window appears, but this module is built lazily
|
|
103
|
+
// -- the first time JavaScript asks for it -- so an app that mounts before
|
|
104
|
+
// then would read a window of no size and never be told otherwise.
|
|
105
|
+
postToUiThread([] { notifyWindowBoundsChanged(); });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
AppKitWindowModule::~AppKitWindowModule() {
|
|
109
|
+
titleBar().setMetricsListener(nullptr);
|
|
110
|
+
setWindowBoundsListener(nullptr);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
Value AppKitWindowModule::setTitle(Runtime &runtime,
|
|
114
|
+
TurboModule & /*module*/,
|
|
115
|
+
const Value *args,
|
|
116
|
+
size_t count) {
|
|
117
|
+
std::optional<std::string> title;
|
|
118
|
+
if (count > 0 && args[0].isString()) {
|
|
119
|
+
title = args[0].getString(runtime).utf8(runtime);
|
|
120
|
+
}
|
|
121
|
+
postToUiThread([title = std::move(title)]() mutable { titleBar().setTitle(std::move(title)); });
|
|
122
|
+
return Value::undefined();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
Value AppKitWindowModule::setTitleBarColors(Runtime & /*runtime*/,
|
|
126
|
+
TurboModule & /*module*/,
|
|
127
|
+
const Value *args,
|
|
128
|
+
size_t count) {
|
|
129
|
+
const auto background = colorArgument(args, count, 0);
|
|
130
|
+
const auto text = colorArgument(args, count, 1);
|
|
131
|
+
const auto border = colorArgument(args, count, 2);
|
|
132
|
+
postToUiThread([background, text, border] { titleBar().setColors(background, text, border); });
|
|
133
|
+
return Value::undefined();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
Value AppKitWindowModule::setTitleBarStyle(Runtime &runtime,
|
|
137
|
+
TurboModule & /*module*/,
|
|
138
|
+
const Value *args,
|
|
139
|
+
size_t count) {
|
|
140
|
+
const bool hidden =
|
|
141
|
+
count > 0 && args[0].isString() && args[0].getString(runtime).utf8(runtime) == "hidden";
|
|
142
|
+
postToUiThread([hidden] {
|
|
143
|
+
titleBar().setStyle(hidden ? TitleBarStyle::Hidden : TitleBarStyle::Native);
|
|
144
|
+
});
|
|
145
|
+
return Value::undefined();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
Value AppKitWindowModule::getTitleBarMetrics(Runtime &runtime,
|
|
149
|
+
TurboModule & /*module*/,
|
|
150
|
+
const Value * /*args*/,
|
|
151
|
+
size_t /*count*/) {
|
|
152
|
+
return metricsValue(runtime, titleBar().metrics());
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
Value AppKitWindowModule::minimize(Runtime & /*runtime*/,
|
|
156
|
+
TurboModule & /*module*/,
|
|
157
|
+
const Value * /*args*/,
|
|
158
|
+
size_t /*count*/) {
|
|
159
|
+
postToUiThread([] { titleBar().minimize(); });
|
|
160
|
+
return Value::undefined();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
Value AppKitWindowModule::toggleMaximize(Runtime & /*runtime*/,
|
|
164
|
+
TurboModule & /*module*/,
|
|
165
|
+
const Value * /*args*/,
|
|
166
|
+
size_t /*count*/) {
|
|
167
|
+
postToUiThread([] { titleBar().toggleMaximize(); });
|
|
168
|
+
return Value::undefined();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
Value AppKitWindowModule::closeWindow(Runtime & /*runtime*/,
|
|
172
|
+
TurboModule & /*module*/,
|
|
173
|
+
const Value * /*args*/,
|
|
174
|
+
size_t /*count*/) {
|
|
175
|
+
postToUiThread([] { titleBar().close(); });
|
|
176
|
+
return Value::undefined();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
Value AppKitWindowModule::startWindowDrag(Runtime & /*runtime*/,
|
|
180
|
+
TurboModule & /*module*/,
|
|
181
|
+
const Value * /*args*/,
|
|
182
|
+
size_t /*count*/) {
|
|
183
|
+
postToUiThread([] { titleBar().startDrag(); });
|
|
184
|
+
return Value::undefined();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
Value AppKitWindowModule::noop(Runtime & /*runtime*/,
|
|
188
|
+
TurboModule & /*module*/,
|
|
189
|
+
const Value * /*args*/,
|
|
190
|
+
size_t /*count*/) {
|
|
191
|
+
return Value::undefined();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
} // namespace basalt
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// macOS's half of the colour-scheme seam.
|
|
2
|
+
//
|
|
3
|
+
// `NSApp.effectiveAppearance` is the authority: it accounts for the system
|
|
4
|
+
// setting, any per-application override in defaults, and the accessibility
|
|
5
|
+
// contrast variants, none of which reading `AppleInterfaceStyle` out of
|
|
6
|
+
// NSUserDefaults would see.
|
|
7
|
+
|
|
8
|
+
#include "ColorScheme.h"
|
|
9
|
+
|
|
10
|
+
#import <Cocoa/Cocoa.h>
|
|
11
|
+
|
|
12
|
+
// Key-value observing needs an object to deliver to, and the C++ half is not
|
|
13
|
+
// one. Observing the *application's* effectiveAppearance rather than the
|
|
14
|
+
// `AppleInterfaceThemeChangedNotification` distributed notification: that one
|
|
15
|
+
// fires for the system setting only, so an app told to be dark on a light
|
|
16
|
+
// system would never hear anything.
|
|
17
|
+
@interface RnAppKitAppearanceObserver : NSObject
|
|
18
|
+
@end
|
|
19
|
+
|
|
20
|
+
@implementation RnAppKitAppearanceObserver
|
|
21
|
+
|
|
22
|
+
- (void)observeValueForKeyPath:(NSString *)keyPath
|
|
23
|
+
ofObject:(id)object
|
|
24
|
+
change:(NSDictionary *)change
|
|
25
|
+
context:(void *)context {
|
|
26
|
+
(void)keyPath;
|
|
27
|
+
(void)object;
|
|
28
|
+
(void)change;
|
|
29
|
+
(void)context;
|
|
30
|
+
basalt::notifyColorSchemeChanged();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@end
|
|
34
|
+
|
|
35
|
+
namespace basalt {
|
|
36
|
+
|
|
37
|
+
namespace {
|
|
38
|
+
|
|
39
|
+
// Kept for the life of the process: it *is* the observer registration, and
|
|
40
|
+
// letting it go would stop the notifications without saying so.
|
|
41
|
+
RnAppKitAppearanceObserver *gObserver = nil;
|
|
42
|
+
|
|
43
|
+
} // namespace
|
|
44
|
+
|
|
45
|
+
ColorScheme systemColorScheme() {
|
|
46
|
+
@autoreleasepool {
|
|
47
|
+
NSAppearance *appearance = NSApp != nil ? NSApp.effectiveAppearance : nil;
|
|
48
|
+
if (appearance == nil) {
|
|
49
|
+
// Before NSApplication exists -- a unit test, or a very early read. Light
|
|
50
|
+
// is React Native's default when a platform has no answer.
|
|
51
|
+
return ColorScheme::Light;
|
|
52
|
+
}
|
|
53
|
+
NSAppearanceName best = [appearance
|
|
54
|
+
bestMatchFromAppearancesWithNames:@[ NSAppearanceNameAqua, NSAppearanceNameDarkAqua ]];
|
|
55
|
+
return [best isEqualToString:NSAppearanceNameDarkAqua] ? ColorScheme::Dark
|
|
56
|
+
: ColorScheme::Light;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
void startObservingColorScheme() {
|
|
61
|
+
if (gObserver != nil || NSApp == nil) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
gObserver = [[RnAppKitAppearanceObserver alloc] init];
|
|
65
|
+
[NSApp addObserver:gObserver
|
|
66
|
+
forKeyPath:@"effectiveAppearance"
|
|
67
|
+
options:NSKeyValueObservingOptionNew
|
|
68
|
+
context:nullptr];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
} // namespace basalt
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// The component registry for the macOS platform. See core/ComponentRegistry.h
|
|
2
|
+
// for why this is per-platform rather than shared.
|
|
3
|
+
//
|
|
4
|
+
// `<View>`, `<Text>`, `<ScrollView>`, `<Image>` and `<TextInput>` -- the
|
|
5
|
+
// components an ordinary app is built from, plus the root; and below them the
|
|
6
|
+
// controls, which are the ones a view layer needs a real toolkit peer for.
|
|
7
|
+
//
|
|
8
|
+
// Paragraph arrived with CoreTextLayoutManager.mm, and could not have arrived
|
|
9
|
+
// before it: registering it constructs a `TextLayoutManager`, whose stub this
|
|
10
|
+
// build drops so a platform's own can be the only definition. That is the link
|
|
11
|
+
// error phase 19 found, and it is what kept this list at one entry.
|
|
12
|
+
//
|
|
13
|
+
// Registering them early would cost nothing visible and be worse than the gap.
|
|
14
|
+
// A registered descriptor with no mounting peer does not error -- Fabric builds
|
|
15
|
+
// the shadow nodes, lays them out, and the app renders blank rectangles where
|
|
16
|
+
// its text should be. Leaving them out means React Native substitutes
|
|
17
|
+
// UnimplementedNativeView, which at least says so.
|
|
18
|
+
|
|
19
|
+
#include "ComponentRegistry.h"
|
|
20
|
+
#include "ExpoImageComponent.h"
|
|
21
|
+
|
|
22
|
+
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
|
|
23
|
+
|
|
24
|
+
#include "SkiaPictureViewComponent.h"
|
|
25
|
+
#include <react/renderer/components/FBReactNativeSpec/ComponentDescriptors.h>
|
|
26
|
+
#include <react/renderer/components/image/ImageComponentDescriptor.h>
|
|
27
|
+
#include <react/renderer/components/iostextinput/TextInputComponentDescriptor.h>
|
|
28
|
+
#include <react/renderer/components/modal/ModalHostViewComponentDescriptor.h>
|
|
29
|
+
#include <react/renderer/components/scrollview/ScrollViewComponentDescriptor.h>
|
|
30
|
+
#include <react/renderer/components/switch/AppleSwitchComponentDescriptor.h>
|
|
31
|
+
#include <react/renderer/components/text/ParagraphComponentDescriptor.h>
|
|
32
|
+
#include <react/renderer/components/text/RawTextComponentDescriptor.h>
|
|
33
|
+
#include <react/renderer/components/text/TextComponentDescriptor.h>
|
|
34
|
+
#include <react/renderer/components/view/ViewComponentDescriptor.h>
|
|
35
|
+
|
|
36
|
+
namespace facebook::react {
|
|
37
|
+
|
|
38
|
+
ComponentRegistryFactory getDefaultComponentRegistryFactory() {
|
|
39
|
+
return [](const EventDispatcher::Weak &eventDispatcher,
|
|
40
|
+
const std::shared_ptr<const ContextContainer> &contextContainer) {
|
|
41
|
+
static auto providerRegistry = []() {
|
|
42
|
+
auto registry = std::make_shared<ComponentDescriptorProviderRegistry>();
|
|
43
|
+
registry->add(concreteComponentDescriptorProvider<ViewComponentDescriptor>());
|
|
44
|
+
// Text is three descriptors and only one of them mounts. <Text> becomes a
|
|
45
|
+
// Text node, its string a RawText node, and the outermost <Text> a
|
|
46
|
+
// Paragraph that folds the whole subtree into a single AttributedString.
|
|
47
|
+
registry->add(concreteComponentDescriptorProvider<ParagraphComponentDescriptor>());
|
|
48
|
+
registry->add(concreteComponentDescriptorProvider<TextComponentDescriptor>());
|
|
49
|
+
registry->add(concreteComponentDescriptorProvider<RawTextComponentDescriptor>());
|
|
50
|
+
// Unlike Image's, ScrollView's descriptor is a bare alias for
|
|
51
|
+
// ConcreteComponentDescriptor -- no manager, no ContextContainer entry.
|
|
52
|
+
// Leaving it out does not fail loudly: the registry silently substitutes
|
|
53
|
+
// UnimplementedNativeView, which has no ScrollViewState, and the symptom
|
|
54
|
+
// is a ScrollView that renders but never scrolls.
|
|
55
|
+
registry->add(concreteComponentDescriptorProvider<ScrollViewComponentDescriptor>());
|
|
56
|
+
// Image's descriptor pulls an ImageManager out of the ContextContainer,
|
|
57
|
+
// creating one if absent. React Native's cxx ImageManager is a stub, so
|
|
58
|
+
// it produces no pixels; AppKitImageLoader does that from the props.
|
|
59
|
+
registry->add(concreteComponentDescriptorProvider<ImageComponentDescriptor>());
|
|
60
|
+
// React Native's *iOS* TextInput, whose C++ is portable: it measures
|
|
61
|
+
// through a TextLayoutManager, which here is the Core Text one.
|
|
62
|
+
// Android's needs fbjni. Its component name is "TextInput".
|
|
63
|
+
registry->add(concreteComponentDescriptorProvider<TextInputComponentDescriptor>());
|
|
64
|
+
// @shopify/react-native-skia's <Canvas>. Registered whether or not the
|
|
65
|
+
// build has Skia in it, for the reason the Expo one below gives: it costs
|
|
66
|
+
// a name in a registry. Without Skia the view mounts and stays empty, and
|
|
67
|
+
// an app that draws has already failed at getEnforcing('RNSkiaModule').
|
|
68
|
+
registry->add(concreteComponentDescriptorProvider<SkiaPictureViewComponentDescriptor>());
|
|
69
|
+
// expo-image's view. Registered whether or not the build has Expo in it:
|
|
70
|
+
// the descriptor is ordinary Fabric C++, and an app that never renders
|
|
71
|
+
// one pays a registry entry. See core/ExpoImageComponent.h.
|
|
72
|
+
registry->add(concreteComponentDescriptorProvider<ExpoImageComponentDescriptor>());
|
|
73
|
+
|
|
74
|
+
// --- The controls -----------------------------------------------------
|
|
75
|
+
//
|
|
76
|
+
// Everything above is a box with something drawn in it. These are not:
|
|
77
|
+
// each one is a toolkit control, or a window, and each one needed the
|
|
78
|
+
// view layer to grow a peer before registering it here would have meant
|
|
79
|
+
// anything. Registering a descriptor with nothing to mount it does not
|
|
80
|
+
// error -- Fabric builds the shadow nodes, lays them out, and the app
|
|
81
|
+
// renders blank rectangles -- so this list and the mounting manager's
|
|
82
|
+
// `hasComponent` are two statements of the same fact and have to agree.
|
|
83
|
+
|
|
84
|
+
// <ActivityIndicator>. React Native's JavaScript writes the 20pt or 36pt
|
|
85
|
+
// box into the view's style, so the shadow node needs no measurement of
|
|
86
|
+
// its own; what the host draws inside it is a spinner.
|
|
87
|
+
registry->add(concreteComponentDescriptorProvider<ActivityIndicatorViewComponentDescriptor>());
|
|
88
|
+
// <Switch>. The descriptor is React Native's iOS one -- portable C++
|
|
89
|
+
// apart from a `measureContent` that instantiates a UISwitch, which
|
|
90
|
+
// core/DesktopControls.cpp replaces with a constant so a switch is the
|
|
91
|
+
// same size on all three desktops.
|
|
92
|
+
registry->add(concreteComponentDescriptorProvider<SwitchComponentDescriptor>());
|
|
93
|
+
// <Modal>. Not a second window: the shadow node is a root-kind node sized
|
|
94
|
+
// from its state and positioned absolutely, so a modal is an overlay
|
|
95
|
+
// filling the surface -- which is also what it is on the web. The host
|
|
96
|
+
// commits the window's size into that state; see
|
|
97
|
+
// core/DesktopControls.h's modalScreenSizeNeedsUpdate for why it must.
|
|
98
|
+
registry->add(concreteComponentDescriptorProvider<ModalHostViewComponentDescriptor>());
|
|
99
|
+
// <RefreshControl>. The spinner a scroll view shows while it refreshes.
|
|
100
|
+
// The gesture that starts it is in core/PullToRefresh.h, because a
|
|
101
|
+
// desktop has no rubber band to release.
|
|
102
|
+
registry->add(concreteComponentDescriptorProvider<PullToRefreshViewComponentDescriptor>());
|
|
103
|
+
// What React Native substitutes for a component no platform registered.
|
|
104
|
+
// Registering it is what turns "nothing happens" into a mounted view the
|
|
105
|
+
// tree dump can show, and it is what the comments above mean when they
|
|
106
|
+
// say the substitution is silent.
|
|
107
|
+
registry->add(concreteComponentDescriptorProvider<UnimplementedNativeViewComponentDescriptor>());
|
|
108
|
+
// React DevTools' element highlighter. It draws nothing here yet -- the
|
|
109
|
+
// highlight arrives as a command rather than as props -- but a DevTools
|
|
110
|
+
// session mounts one, and an unregistered component in the tree is a
|
|
111
|
+
// blank rectangle in the middle of the app.
|
|
112
|
+
registry->add(concreteComponentDescriptorProvider<DebuggingOverlayComponentDescriptor>());
|
|
113
|
+
|
|
114
|
+
return registry;
|
|
115
|
+
}();
|
|
116
|
+
return providerRegistry->createComponentDescriptorRegistry(
|
|
117
|
+
{eventDispatcher, contextContainer, nullptr});
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
} // namespace facebook::react
|