basalt-appkit 0.0.0-rc5 → 0.2.0

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 (43) hide show
  1. package/native/CMakeLists.txt +16 -2
  2. package/native/appkit/AppKitAppWindow.h +24 -0
  3. package/native/appkit/AppKitImageLoader.h +48 -1
  4. package/native/appkit/AppKitImageLoader.mm +228 -10
  5. package/native/appkit/AppKitMenuBar.mm +58 -0
  6. package/native/appkit/AppKitMountingManager.h +34 -0
  7. package/native/appkit/AppKitMountingManager.mm +582 -12
  8. package/native/appkit/AppKitScrollView.h +48 -0
  9. package/native/appkit/AppKitScrollView.mm +210 -0
  10. package/native/appkit/AppKitSkiaPeer.h +15 -0
  11. package/native/appkit/AppKitTextInput.h +38 -1
  12. package/native/appkit/AppKitTextInput.mm +235 -8
  13. package/native/appkit/AppKitTextPeer.h +60 -0
  14. package/native/appkit/AppKitTextPeer.mm +307 -0
  15. package/native/appkit/AppKitTouchDispatcher.h +56 -0
  16. package/native/appkit/AppKitTouchDispatcher.mm +140 -0
  17. package/native/appkit/AppKitWindowControl.mm +38 -3
  18. package/native/appkit/ComponentRegistryAppKit.mm +28 -3
  19. package/native/appkit/CoreTextLayout.h +64 -2
  20. package/native/appkit/CoreTextLayout.mm +431 -71
  21. package/native/appkit/CoreTextLayoutManager.mm +57 -6
  22. package/native/appkit/FontRegistryCoreText.mm +6 -5
  23. package/native/appkit/PlatformServicesAppKit.mm +101 -20
  24. package/native/appkit/RnAppKitView.h +492 -0
  25. package/native/appkit/RnAppKitView.mm +2266 -23
  26. package/native/appkit/RnTextLayout.h +106 -0
  27. package/native/appkit/RnTextLayout.mm +321 -2
  28. package/native/appkit/main_appkit.mm +174 -3
  29. package/native/tests/test_appkit_accessibility.mm +289 -0
  30. package/native/tests/test_appkit_borders.mm +975 -0
  31. package/native/tests/test_appkit_fonts.mm +203 -0
  32. package/native/tests/test_appkit_image.mm +392 -17
  33. package/native/tests/test_appkit_input.mm +209 -0
  34. package/native/tests/test_appkit_mounting.mm +360 -0
  35. package/native/tests/test_appkit_scrollbar.mm +125 -0
  36. package/native/tests/test_appkit_scrollview.mm +371 -0
  37. package/native/tests/test_appkit_selection.mm +349 -0
  38. package/native/tests/test_appkit_services.mm +51 -1
  39. package/native/tests/test_appkit_text.mm +1628 -0
  40. package/native/tests/test_appkit_textinput.mm +819 -0
  41. package/native/tests/test_appkit_titlebar.mm +43 -0
  42. package/native/tests/test_appkit_view.mm +609 -0
  43. package/package.json +4 -3
@@ -14,6 +14,8 @@
14
14
  #include <vector>
15
15
  #include "WindowControl.h"
16
16
 
17
+ #import "AppKitAppWindow.h"
18
+
17
19
  #import <Cocoa/Cocoa.h>
18
20
 
19
21
  namespace basalt {
@@ -21,9 +23,27 @@ namespace basalt {
21
23
  namespace {
22
24
 
23
25
 
24
- NSWindow *window() {
25
- return NSApp.keyWindow != nil ? NSApp.keyWindow : NSApp.mainWindow;
26
- }
26
+ // The window this process owns, which is a different question from which window
27
+ // has the keyboard.
28
+ //
29
+ // `NSApp.keyWindow` and `NSApp.mainWindow` are both nil whenever the app is not
30
+ // the active one: a background app has no key window and no main window. Asking
31
+ // only those two meant an app that was not frontmost read a *zero* size out of
32
+ // `getBounds()` and had `setSize` silently do nothing.
33
+ //
34
+ // That is worse than it sounds, because the bounds cache is primed once at
35
+ // startup, immediately after `activateIgnoringOtherApps:`, and activation is
36
+ // asynchronous. If the window server had not granted it by then the cache was
37
+ // zeroed, and nothing corrected it: the only thing that writes the cache again
38
+ // is a move or a resize, and a window nobody is using gets neither. An app
39
+ // launched while another window had focus therefore believed it was 0x0 for its
40
+ // whole life, which is how `e2e/window.tsx` found this.
41
+ //
42
+ // GTK had the rule right already -- GtkWindowControl.cpp prefers the active
43
+ // toplevel and falls back to the first visible one -- so this is the same rule
44
+ // rather than a new idea. A panel is skipped: LogBox and the dev menu are
45
+ // windows too, and neither is what an app means by "my window".
46
+ NSWindow *window() { return appWindow(); }
27
47
 
28
48
  // The height AppKit measures y from. `NSScreen.screens.firstObject` is the
29
49
  // primary display, which is the one AppKit's global coordinates are anchored
@@ -35,6 +55,21 @@ CGFloat primaryHeight() {
35
55
 
36
56
  } // namespace
37
57
 
58
+ NSWindow *appWindow() {
59
+ if (NSApp.keyWindow != nil) {
60
+ return NSApp.keyWindow;
61
+ }
62
+ if (NSApp.mainWindow != nil) {
63
+ return NSApp.mainWindow;
64
+ }
65
+ for (NSWindow *candidate in NSApp.windows) {
66
+ if (candidate.isVisible && ![candidate isKindOfClass:NSPanel.class]) {
67
+ return candidate;
68
+ }
69
+ }
70
+ return nil;
71
+ }
72
+
38
73
  std::vector<DisplayInfo> displays() {
39
74
  std::vector<DisplayInfo> found;
40
75
  @autoreleasepool {
@@ -18,10 +18,13 @@
18
18
 
19
19
  #include "ComponentRegistry.h"
20
20
  #include "ExpoImageComponent.h"
21
+ #include "SafeAreaComponent.h"
22
+ #include "ScreensComponent.h"
21
23
 
22
24
  #include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
23
25
 
24
26
  #include "SkiaPictureViewComponent.h"
27
+ #include "SkiaViewComponent.h"
25
28
  #include <react/renderer/components/FBReactNativeSpec/ComponentDescriptors.h>
26
29
  #include <react/renderer/components/image/ImageComponentDescriptor.h>
27
30
  #include <react/renderer/components/iostextinput/TextInputComponentDescriptor.h>
@@ -41,6 +44,24 @@ ComponentRegistryFactory getDefaultComponentRegistryFactory() {
41
44
  static auto providerRegistry = []() {
42
45
  auto registry = std::make_shared<ComponentDescriptorProviderRegistry>();
43
46
  registry->add(concreteComponentDescriptorProvider<ViewComponentDescriptor>());
47
+ // react-native-safe-area-context's provider. Registered on every
48
+ // platform, like expo-image's above: the descriptor is ordinary Fabric
49
+ // C++ and an app that never mounts one pays a registry entry. Without
50
+ // it the provider renders its children never, not badly. See
51
+ // core/SafeAreaComponent.h.
52
+ registry->add(concreteComponentDescriptorProvider<SafeAreaProviderComponentDescriptor>());
53
+ // react-native-screens. RNSScreen carries activityState, which is what
54
+ // hides the screen behind the one in front; the rest are containers that
55
+ // only need their names known, since an unregistered name becomes
56
+ // UnimplementedNativeView and carries none of the layout props. See
57
+ // core/ScreensComponent.h.
58
+ registry->add(concreteComponentDescriptorProvider<RNSScreenComponentDescriptor>());
59
+ registry->add(concreteComponentDescriptorProvider<RNSScreenStackComponentDescriptor>());
60
+ registry->add(concreteComponentDescriptorProvider<RNSScreenContainerComponentDescriptor>());
61
+ registry->add(concreteComponentDescriptorProvider<RNSScreenNavigationContainerComponentDescriptor>());
62
+ registry->add(concreteComponentDescriptorProvider<RNSScreenContentWrapperComponentDescriptor>());
63
+ registry->add(concreteComponentDescriptorProvider<RNSScreenStackHeaderConfigComponentDescriptor>());
64
+ registry->add(concreteComponentDescriptorProvider<RNSScreenStackHeaderSubviewComponentDescriptor>());
44
65
  // Text is three descriptors and only one of them mounts. <Text> becomes a
45
66
  // Text node, its string a RawText node, and the outermost <Text> a
46
67
  // Paragraph that folds the whole subtree into a single AttributedString.
@@ -61,11 +82,15 @@ ComponentRegistryFactory getDefaultComponentRegistryFactory() {
61
82
  // through a TextLayoutManager, which here is the Core Text one.
62
83
  // Android's needs fbjni. Its component name is "TextInput".
63
84
  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
85
+ // React Native Skia's <Canvas>, under both of its names: 2.x mounts a
86
+ // "SkiaPictureView" and 3.x a "SkiaView", and which one an app mounts is
87
+ // decided by the Skia it installed, which a host binary cannot know
88
+ // until the app's JavaScript runs. Registered whether or not the build
89
+ // has Skia in it, for the reason the Expo one below gives: it costs a
90
+ // name in a registry. Without Skia the view mounts and stays empty, and
67
91
  // an app that draws has already failed at getEnforcing('RNSkiaModule').
68
92
  registry->add(concreteComponentDescriptorProvider<SkiaPictureViewComponentDescriptor>());
93
+ registry->add(concreteComponentDescriptorProvider<SkiaViewComponentDescriptor>());
69
94
  // expo-image's view. Registered whether or not the build has Expo in it:
70
95
  // the descriptor is ordinary Fabric C++, and an app that never renders
71
96
  // one pays a registry entry. See core/ExpoImageComponent.h.
@@ -24,6 +24,9 @@
24
24
 
25
25
  #import "RnTextLayout.h"
26
26
 
27
+ #include "FontFitting.h"
28
+ #include "TextAlignments.h"
29
+
27
30
  #include <react/renderer/attributedstring/AttributedString.h>
28
31
  #include <react/renderer/attributedstring/ParagraphAttributes.h>
29
32
  #include <react/renderer/attributedstring/TextAttributes.h>
@@ -31,13 +34,72 @@
31
34
  namespace basalt {
32
35
 
33
36
  // Builds a layout for `attributedString`.
37
+ //
38
+ // `fit` is `adjustsFontSizeToFit`'s answer: the ratio every font size is
39
+ // multiplied by and the two bounds it is clamped against. The default is what a
40
+ // paragraph that never asked gets, which changes nothing -- see
41
+ // core/FontFitting.h.
34
42
  RnTextLayout *buildTextLayout(const facebook::react::AttributedString &attributedString,
35
- const facebook::react::ParagraphAttributes &paragraphAttributes);
43
+ const facebook::react::ParagraphAttributes &paragraphAttributes,
44
+ basalt::FontFit fit = {});
45
+
46
+ // How far this paragraph's fonts have to shrink to fit a box, for
47
+ // `adjustsFontSizeToFit`.
48
+ //
49
+ // The search is core's; this is the half that measures, which means building a
50
+ // layout per probe -- about eight of them -- and is why it runs only for a
51
+ // paragraph that asked. Both the measurement seam and the mounting manager call
52
+ // it, with the constraints and the frame respectively, so the text is painted
53
+ // at the size it was measured at.
54
+ basalt::FontFit textFitScale(const facebook::react::AttributedString &attributedString,
55
+ const facebook::react::ParagraphAttributes &paragraphAttributes,
56
+ float maxWidth,
57
+ float maxHeight);
58
+
59
+ // One fragment's text with its `textTransform` applied, which has to happen
60
+ // before anything measures the string *and* before anything indexes into it: a
61
+ // transform can change the length -- ß uppercases to SS -- so an attachment
62
+ // after one would otherwise be looked up at the wrong offset. Shared with
63
+ // CoreTextLayoutManager.mm for exactly that reason.
64
+ NSString *transformedFragmentText(const facebook::react::AttributedString::Fragment &fragment,
65
+ NSString *text);
36
66
 
37
67
  // The attributes for a whole string, for anything that holds its own text
38
68
  // rather than a layout built here -- the equivalent of Pango's
39
69
  // buildTextAttributes, and what a <TextInput> will need.
70
+ // `directions` is which way the text runs and what `start` and `end` mean,
71
+ // which the relative alignments need; see core/TextAlignments.h. A caller that
72
+ // holds its own text and resolves its own direction -- an NSTextField --
73
+ // passes neither and leaves `natural` to AppKit.
40
74
  NSDictionary<NSAttributedStringKey, id> *buildTextAttributes(
41
- const facebook::react::TextAttributes &textAttributes);
75
+ const facebook::react::TextAttributes &textAttributes,
76
+ basalt::FontFit fit = {},
77
+ basalt::TextDirections directions = {});
78
+
79
+ // `fontVariant` in Core Text's vocabulary, which is not OpenType's: Apple's
80
+ // older AAT pairs, a feature type and a selector inside it, as upstream's
81
+ // `RCTFontFeatures` uses.
82
+ //
83
+ // Exposed for the suite rather than for callers. The mapping is the part this
84
+ // platform owns and a wrong selector would be invisible: `[NSFont
85
+ // fontWithDescriptor:]` resolves against a real font and **drops features that
86
+ // font does not have**, so asking the resolved font what it kept measures the
87
+ // machine's fonts rather than this code. The system font has small caps and
88
+ // tabular figures and has neither oldstyle figures nor a twentieth stylistic
89
+ // set, which is how that was found.
90
+ NSArray *fontFeaturesFor(const facebook::react::TextAttributes &textAttributes);
91
+
92
+ // The desktop's text scale, which on macOS is 1 and is a function anyway.
93
+ //
94
+ // macOS publishes no scalar for this. Accessibility's "Text size" works through
95
+ // the text-style ramp instead: an app opts in by asking
96
+ // `[NSFont preferredFontForTextStyle:options:]` for its fonts, and the system
97
+ // answers a larger font for the style. That is `dynamicTypeRamp`'s shape rather
98
+ // than `fontSizeMultiplier`'s, and it is a different feature; backlog/text.md
99
+ // records it with that call named.
100
+ //
101
+ // So this exists to say so in one place, and to keep the host's wiring the same
102
+ // shape as GTK's, which does have a scale to read.
103
+ float appKitTextScale();
42
104
 
43
105
  } // namespace basalt