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
@@ -11,11 +11,12 @@
11
11
  // and to nothing else on the machine, which is what an app loading a bundled
12
12
  // font wants and the only scope that needs no user consent.
13
13
  //
14
- // Nothing on this platform renders text yet -- there is no Core Text
15
- // TextLayoutManager, which is the seam phase 19 found. So `resolveFontFamily`
16
- // currently has no reader. It is implemented anyway, because the alternative is
17
- // a registry that silently records nothing and a font milestone that starts by
18
- // debugging this file.
14
+ // `resolveFontFamily` had no reader when this was written: there was no Core
15
+ // Text TextLayoutManager, the seam phase 19 found. There is one now, and
16
+ // CoreTextLayout.mm's `fontFor` asks this for every paragraph with a
17
+ // `fontFamily`. tests/test_appkit_fonts.mm is where that path is checked,
18
+ // including a family the machine does not have, which is the case a loader
19
+ // exists for.
19
20
 
20
21
  #include "FontRegistry.h"
21
22
 
@@ -1,6 +1,7 @@
1
1
  // macOS's half of the platform-services seam: clipboard, opening a URL, alerts.
2
2
 
3
3
  #include "PlatformServices.h"
4
+ #include "SystemLocale.h"
4
5
  #include "TestDialog.h"
5
6
 
6
7
  #include <cstdint>
@@ -379,6 +380,67 @@ NSString *keyEquivalentFor(const std::string &shortcut, NSEventModifierFlags *ma
379
380
  }
380
381
  }
381
382
 
383
+ // Builds `entries` into `menu`, numbering every entry the way `walkMenuEntries`
384
+ // does: pre-order, counting separators and submenu parents, so a flat menu is
385
+ // numbered by position and nothing a caller already relied on moved.
386
+ //
387
+ // A parent carries no action and no tag. Choosing one is not a choice, it is
388
+ // opening a menu, and AppKit does not call an action for it anyway.
389
+ void addMenuEntries(NSMenu *menu,
390
+ const std::vector<MenuEntry> &entries,
391
+ RnAppKitMenuTarget *target,
392
+ int *next) {
393
+ for (const MenuEntry &entry : entries) {
394
+ const int index = (*next)++;
395
+ // Counted above, drawn or not: a role this desktop cannot perform keeps its
396
+ // index so the ones after it keep theirs. See menuEntryShown.
397
+ if (!menuEntryShown(entry)) {
398
+ continue;
399
+ }
400
+ if (entry.isSeparator()) {
401
+ [menu addItem:[NSMenuItem separatorItem]];
402
+ continue;
403
+ }
404
+
405
+ NSString *label = [NSString stringWithUTF8String:entry.label.c_str()];
406
+ if (label == nil) {
407
+ label = @"";
408
+ }
409
+
410
+ if (entry.isParent()) {
411
+ NSMenuItem *parent = [menu addItemWithTitle:label action:nullptr keyEquivalent:@""];
412
+ parent.enabled = entry.enabled;
413
+ NSMenu *submenu = [[NSMenu alloc] initWithTitle:label];
414
+ submenu.autoenablesItems = NO;
415
+ addMenuEntries(submenu, entry.submenu, target, next);
416
+ parent.submenu = submenu;
417
+ continue;
418
+ }
419
+
420
+ NSEventModifierFlags mask = 0;
421
+ NSString *equivalent = keyEquivalentFor(entry.shortcut, &mask);
422
+ NSMenuItem *item = [menu addItemWithTitle:label
423
+ action:@selector(pick:)
424
+ keyEquivalent:equivalent];
425
+ item.keyEquivalentModifierMask = mask;
426
+ // Our own target even for a role, unlike the menu bar's `nil`: this has a
427
+ // promise to settle with the index, so it has to hear about the choice. The
428
+ // role is performed below, once, before the index is reported.
429
+ item.target = target;
430
+ item.enabled = entry.enabled;
431
+ item.tag = static_cast<NSInteger>(index);
432
+
433
+ // Both kinds draw a tick. NSMenu has no radio item, and a tick is what
434
+ // Apple's guidance puts beside the chosen member of an exclusive group, so
435
+ // this is the platform's answer rather than a shortfall. NSMenu manages the
436
+ // indicator column itself, so an unticked item still lines up with a ticked
437
+ // one and nothing has to reserve space.
438
+ if (entry.mark != MenuEntry::Mark::None) {
439
+ item.state = entry.checked ? NSControlStateValueOn : NSControlStateValueOff;
440
+ }
441
+ }
442
+ }
443
+
382
444
  } // namespace
383
445
 
384
446
  void showMenu(const MenuRequest &request, MenuCallback onChosen) {
@@ -403,25 +465,8 @@ void showMenu(const MenuRequest &request, MenuCallback onChosen) {
403
465
  menu.autoenablesItems = NO;
404
466
  RnAppKitMenuTarget *target = [[RnAppKitMenuTarget alloc] init];
405
467
 
406
- for (size_t i = 0; i < content.entries.size(); i++) {
407
- const MenuEntry &entry = content.entries[i];
408
- if (entry.isSeparator()) {
409
- [menu addItem:[NSMenuItem separatorItem]];
410
- continue;
411
- }
412
- NSEventModifierFlags mask = 0;
413
- NSString *equivalent = keyEquivalentFor(entry.shortcut, &mask);
414
- NSString *label = [NSString stringWithUTF8String:entry.label.c_str()];
415
- NSMenuItem *item = [menu addItemWithTitle:label != nil ? label : @""
416
- action:@selector(pick:)
417
- keyEquivalent:equivalent];
418
- item.keyEquivalentModifierMask = mask;
419
- item.target = target;
420
- item.enabled = entry.enabled;
421
- // The index the caller gets back, which counts separators -- so it is
422
- // the position in the vector rather than in this menu.
423
- item.tag = static_cast<NSInteger>(i);
424
- }
468
+ int next = 0;
469
+ addMenuEntries(menu, content.entries, target, &next);
425
470
 
426
471
  NSPoint at;
427
472
  if (content.x >= 0.0 && content.y >= 0.0) {
@@ -438,7 +483,18 @@ void showMenu(const MenuRequest &request, MenuCallback onChosen) {
438
483
  // Blocks until the menu is dismissed, which is what a menu does. Safe
439
484
  // here and not on the JavaScript thread; see above.
440
485
  [menu popUpMenuPositioningItem:nil atLocation:at inView:anchor];
441
- onChosen(static_cast<int>(target.chosen));
486
+
487
+ const int chosen = static_cast<int>(target.chosen);
488
+ // The platform's half of a role, before the caller's. `copy` has to have
489
+ // copied by the time JavaScript's `onSelect` runs, because an app whose
490
+ // handler reads the clipboard is reading what this just put there.
491
+ if (chosen >= 0) {
492
+ const MenuEntry *entry = menuEntryAt(content.entries, chosen);
493
+ if (entry != nullptr && !entry->role.empty()) {
494
+ performMenuRole(entry->role);
495
+ }
496
+ }
497
+ onChosen(chosen);
442
498
  }
443
499
  });
444
500
  }
@@ -538,3 +594,28 @@ bool isUiThread() {
538
594
  }
539
595
 
540
596
  } // namespace basalt
597
+
598
+ // --- The desktop's language --------------------------------------------------
599
+
600
+ namespace basalt {
601
+
602
+ // `characterDirectionForLanguage:` is ICU's own answer, which is what every
603
+ // Cocoa application uses to decide whether to mirror -- so an app on this host
604
+ // mirrors exactly when the desktop around it does. Asking the language rather
605
+ // than the full identifier is deliberate: `ar_EG` and `ar` read the same way,
606
+ // and the territory never decides direction.
607
+ SystemLocale systemLocale() {
608
+ SystemLocale locale;
609
+ NSLocale *const current = NSLocale.currentLocale;
610
+ locale.identifier = current.localeIdentifier.UTF8String != nullptr
611
+ ? current.localeIdentifier.UTF8String
612
+ : "";
613
+ NSString *const language = [current objectForKey:NSLocaleLanguageCode];
614
+ if (language != nil) {
615
+ locale.rightToLeft = [NSLocale characterDirectionForLanguage:language] ==
616
+ NSLocaleLanguageDirectionRightToLeft;
617
+ }
618
+ return locale;
619
+ }
620
+
621
+ } // namespace basalt