@legendapp/spark 0.0.1-next.2 → 0.0.1-next.4
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/README.md +10 -4
- package/ai-codex.ts +1 -0
- package/ai.ts +1 -0
- package/app-documents.ts +1 -0
- package/camera.ts +13 -0
- package/config-plugin.d.cts +2 -0
- package/contracts.ts +2 -0
- package/diagnostics.ts +1 -0
- package/expo-config.d.cts +1 -0
- package/metro.cjs +4 -1
- package/metro.d.cts +12 -0
- package/native.cjs +3 -1
- package/native.d.cts +6 -0
- package/package.json +93 -15
- package/patches/camera/react-native-nitro-image.patch +320 -0
- package/patches/camera/react-native-nitro-modules.patch +50 -0
- package/patches/camera/react-native-vision-camera.patch +2662 -0
- package/patches/camera/upstream.json +22 -0
- package/patches/dependencies.json +8 -0
- package/patches/react-native-enriched-markdown@1.0.2.patch +8684 -0
- package/postinstall.cjs +1 -0
- package/schema.json +276 -58
- package/settings-observable.ts +1 -0
- package/settings-window.ts +1 -0
- package/shortcuts-commands.ts +1 -0
- package/shortcuts-keyboard.ts +3 -0
- package/ui-classnames.ts +1 -0
- package/ui-glass.ts +1 -0
- package/ui-search.ts +1 -0
- package/ui-sidebar.ts +1 -0
- package/ui-split-view.ts +1 -0
- package/ui-swipe.ts +1 -0
- package/ui-symbol.ts +1 -0
- package/vendor/node_modules/@legendapp/spark-ai/package.json +19 -0
- package/vendor/node_modules/@legendapp/spark-ai/src/availability.ts +17 -0
- package/vendor/node_modules/@legendapp/spark-ai/src/index.ts +15 -0
- package/vendor/node_modules/@legendapp/spark-ai/src/invocations.ts +34 -0
- package/vendor/node_modules/@legendapp/spark-ai/src/output.ts +62 -0
- package/vendor/node_modules/@legendapp/spark-ai/src/run.ts +12 -0
- package/vendor/node_modules/@legendapp/spark-ai/src/types.ts +16 -0
- package/vendor/node_modules/@legendapp/spark-audio/macos/RNSparkAudio.h +2 -1
- package/vendor/node_modules/@legendapp/spark-audio/macos/RNSparkAudio.mm +126 -24
- package/vendor/node_modules/@legendapp/spark-audio/package.json +5 -2
- package/vendor/node_modules/@legendapp/spark-audio/src/NativeSparkAudio.ts +5 -1
- package/vendor/node_modules/@legendapp/spark-audio/src/desktop.ts +36 -32
- package/vendor/node_modules/@legendapp/spark-audio/src/events.ts +11 -0
- package/vendor/node_modules/@legendapp/spark-audio/src/hooks.ts +65 -0
- package/vendor/node_modules/@legendapp/spark-audio/src/index.web.ts +92 -33
- package/vendor/node_modules/@legendapp/spark-audio/src/media-session-unavailable.ts +6 -4
- package/vendor/node_modules/@legendapp/spark-audio/src/media-session.ts +52 -19
- package/vendor/node_modules/@legendapp/spark-audio/src/media-session.web.ts +18 -10
- package/vendor/node_modules/@legendapp/spark-audio/src/media-types.ts +40 -13
- package/vendor/node_modules/@legendapp/spark-audio/src/mobile.ts +40 -32
- package/vendor/node_modules/@legendapp/spark-audio/src/player.ts +97 -0
- package/vendor/node_modules/@legendapp/spark-audio/src/status-listeners.ts +21 -15
- package/vendor/node_modules/@legendapp/spark-audio/src/types.ts +9 -4
- package/vendor/node_modules/@legendapp/spark-audio/windows/SparkAudio/SparkAudio.h +67 -20
- package/vendor/node_modules/@legendapp/spark-auth-session/package.json +4 -3
- package/vendor/node_modules/@legendapp/spark-auth-session/src/core.ts +60 -42
- package/vendor/node_modules/@legendapp/spark-auth-session/src/index.ts +28 -12
- package/vendor/node_modules/@legendapp/spark-auth-session/src/mobile.ts +97 -41
- package/vendor/node_modules/@legendapp/spark-auth-session/src/types.ts +20 -9
- package/vendor/node_modules/@legendapp/spark-cli/dist/add-desktop.js +50 -7
- package/vendor/node_modules/@legendapp/spark-cli/dist/build-mode.js +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/dist/build.js +145 -59
- package/vendor/node_modules/@legendapp/spark-cli/dist/cli-arguments.js +61 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/commands.js +10 -4
- package/vendor/node_modules/@legendapp/spark-cli/dist/create.js +37 -8
- package/vendor/node_modules/@legendapp/spark-cli/dist/credentials.js +86 -25
- package/vendor/node_modules/@legendapp/spark-cli/dist/dependency-pins.js +132 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/dev-command.js +13 -4
- package/vendor/node_modules/@legendapp/spark-cli/dist/dev.js +13 -10
- package/vendor/node_modules/@legendapp/spark-cli/dist/errors.js +100 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/examples.js +2 -2
- package/vendor/node_modules/@legendapp/spark-cli/dist/expo-dev-patch.cjs +4 -2
- package/vendor/node_modules/@legendapp/spark-cli/dist/expo-metro.cjs +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/dist/expo-native.cjs +5 -1
- package/vendor/node_modules/@legendapp/spark-cli/dist/expo-node.js +3 -3
- package/vendor/node_modules/@legendapp/spark-cli/dist/helpers.js +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/dist/index.js +52 -44
- package/vendor/node_modules/@legendapp/spark-cli/dist/json.js +16 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/local.js +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/dist/metro.cjs +26 -5
- package/vendor/node_modules/@legendapp/spark-cli/dist/native-metadata.js +26 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/native-patch-preflight.js +86 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/native-preparation.js +88 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/notary-profiles.js +75 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/package.js +4 -2
- package/vendor/node_modules/@legendapp/spark-cli/dist/platform.js +16 -2
- package/vendor/node_modules/@legendapp/spark-cli/dist/process.js +7 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/project.js +90 -32
- package/vendor/node_modules/@legendapp/spark-cli/dist/release.js +86 -5
- package/vendor/node_modules/@legendapp/spark-cli/dist/release.json +23 -18
- package/vendor/node_modules/@legendapp/spark-cli/dist/runner-install.js +145 -31
- package/vendor/node_modules/@legendapp/spark-cli/dist/signing.js +4 -3
- package/vendor/node_modules/@legendapp/spark-cli/dist/universal.cjs +2 -1
- package/vendor/node_modules/@legendapp/spark-cli/dist/updates.js +41 -4
- package/vendor/node_modules/@legendapp/spark-cli/dist/windows.js +5 -9
- package/vendor/node_modules/@legendapp/spark-cli/package.json +7 -6
- package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/App.tsx +6 -12
- package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/desktop.config.json +5 -3
- package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/package.json +12 -10
- package/vendor/node_modules/@legendapp/spark-cli/templates/diff-lite/Git.macos.tsx +2 -2
- package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/Integration.macos.tsx +23 -21
- package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/Integration.windows.tsx +23 -21
- package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/io.desktop.ts +11 -8
- package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/io.macos.ts +3 -3
- package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/Lifecycle.desktop.tsx +8 -14
- package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/OpenFiles.desktop.tsx +5 -10
- package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/io.desktop.ts +11 -8
- package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/io.macos.ts +3 -3
- package/vendor/node_modules/@legendapp/spark-cli/templates/music-lite/assets.desktop.ts +2 -2
- package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/Activation.desktop.tsx +2 -2
- package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/Desktop.desktop.ts +37 -15
- package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/session.ts +5 -6
- package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/window-session.ts +5 -4
- package/vendor/node_modules/@legendapp/spark-cli/templates/universal/desktop.config.json +5 -3
- package/vendor/node_modules/@legendapp/spark-cli/templates/universal/metro.config.js +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/templates/universal/package.json +14 -12
- package/vendor/node_modules/@legendapp/spark-cli/templates/universal/react-native.config.js +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/templates/windows/package.json +14 -12
- package/vendor/node_modules/@legendapp/spark-cli/templates/windows/react-native.config.js +1 -1
- package/vendor/node_modules/@legendapp/spark-clipboard/macos/RNDesktopClipboard.mm +10 -16
- package/vendor/node_modules/@legendapp/spark-clipboard/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-clipboard/src/NativeDesktopClipboard.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-clipboard/src/desktop.ts +55 -31
- package/vendor/node_modules/@legendapp/spark-clipboard/src/expo.ts +21 -11
- package/vendor/node_modules/@legendapp/spark-clipboard/src/options.ts +8 -0
- package/vendor/node_modules/@legendapp/spark-clipboard/src/types.ts +10 -0
- package/vendor/node_modules/@legendapp/spark-clipboard/windows/SparkClipboard/SparkClipboard.h +38 -15
- package/vendor/node_modules/@legendapp/spark-clipboard/windows/SparkClipboard/SparkClipboard.vcxproj +2 -0
- package/vendor/node_modules/@legendapp/spark-codex/RNCodex.podspec +22 -0
- package/vendor/node_modules/@legendapp/spark-codex/cpp/HybridCodexAppServer.hpp +23 -0
- package/vendor/node_modules/@legendapp/spark-codex/cpp/HybridCodexAppServer.mm +1529 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitro.json +21 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/.gitattributes +1 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex+autolinking.rb +62 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Bridge.cpp +17 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Bridge.hpp +27 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Umbrella.hpp +38 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodexAutolinking.mm +35 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodexAutolinking.swift +16 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/CodexAvailability.hpp +95 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/CodexRunResult.hpp +99 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/HybridCodexAppServerSpec.cpp +24 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/HybridCodexAppServerSpec.hpp +71 -0
- package/vendor/node_modules/@legendapp/spark-codex/package.json +35 -0
- package/vendor/node_modules/@legendapp/spark-codex/react-native.config.js +11 -0
- package/vendor/node_modules/@legendapp/spark-codex/src/Codex.nitro.ts +30 -0
- package/vendor/node_modules/@legendapp/spark-codex/src/index.ts +65 -0
- package/vendor/node_modules/@legendapp/spark-codex/src/native.ts +6 -0
- package/vendor/node_modules/@legendapp/spark-commands/package.json +21 -0
- package/vendor/node_modules/@legendapp/spark-commands/src/bindings.ts +90 -0
- package/vendor/node_modules/@legendapp/spark-commands/src/index.tsx +400 -0
- package/vendor/node_modules/@legendapp/spark-commands/src/router.ts +138 -0
- package/vendor/node_modules/@legendapp/spark-commands/src/storage.ts +20 -0
- package/vendor/node_modules/@legendapp/spark-context-menu/macos/RNContextMenu.mm +7 -8
- package/vendor/node_modules/@legendapp/spark-context-menu/package.json +4 -3
- package/vendor/node_modules/@legendapp/spark-context-menu/src/NativeContextMenu.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-context-menu/src/index.ts +26 -26
- package/vendor/node_modules/@legendapp/spark-context-menu/windows/SparkContextMenu/SparkContextMenu.h +3 -2
- package/vendor/node_modules/@legendapp/spark-desktop-app/RNDesktopApp.podspec +2 -1
- package/vendor/node_modules/@legendapp/spark-desktop-app/cpp/SparkBinaryJSI.hpp +89 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/cpp/SparkBinaryWindows.hpp +22 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/RNDesktopApp.mm +13 -7
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkBinaryJSI.h +48 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktop.h +4 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktop.mm +33 -33
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktopError.h +20 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkLifecycle.h +26 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkLifecycle.mm +49 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkQuitCoordinator.h +9 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkQuitCoordinator.mm +70 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkWindow.mm +9 -6
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/recent-documents/RNRecentDocumentController.mm +55 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/package.json +5 -3
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/NativeDesktopApp.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/api.ts +86 -40
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/accelerator.ts +40 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/index.ts +56 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/menu.ts +116 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/native-buffer.ts +22 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/path.ts +24 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/events.ts +75 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/index.ts +2 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/index.windows.ts +2 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/open-requests.ts +43 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/restoration.ts +11 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/transport.ts +10 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/app.plugin.cjs +10 -5
- package/vendor/node_modules/@legendapp/spark-desktop-config/app.plugin.d.cts +3 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/autolinking.cjs +35 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/config.cjs +18 -3
- package/vendor/node_modules/@legendapp/spark-desktop-config/config.d.cts +89 -9
- package/vendor/node_modules/@legendapp/spark-desktop-config/expo.cjs +1 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/expo.d.cts +3 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/identity.cjs +3 -2
- package/vendor/node_modules/@legendapp/spark-desktop-config/lifecycle.cjs +20 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/native-patches.cjs +106 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/native-patches.d.cts +1 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/package.json +7 -5
- package/vendor/node_modules/@legendapp/spark-desktop-config/schema.json +276 -58
- package/vendor/node_modules/@legendapp/spark-desktop-config/updates.cjs +1 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/validation.cjs +35 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/windows.plugin.cjs +2 -1
- package/vendor/node_modules/@legendapp/spark-desktop-host/AppDelegate.mm +129 -11
- package/vendor/node_modules/@legendapp/spark-desktop-host/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-desktop-host/windows/application.inc +277 -121
- package/vendor/node_modules/@legendapp/spark-desktop-links/common/AuthLoopback.h +5 -7
- package/vendor/node_modules/@legendapp/spark-desktop-links/macos/RNDesktopLinks.mm +10 -9
- package/vendor/node_modules/@legendapp/spark-desktop-links/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-desktop-links/src/NativeDesktopLinks.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-desktop-links/src/api.ts +27 -33
- package/vendor/node_modules/@legendapp/spark-desktop-links/src/expo.ts +3 -4
- package/vendor/node_modules/@legendapp/spark-desktop-links/src/native.ts +9 -0
- package/vendor/node_modules/@legendapp/spark-desktop-links/windows/SparkLinks/SparkLinks.h +22 -22
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/RNDesktopShortcuts.podspec +1 -0
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/RNDesktopShortcuts.mm +1 -1
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/keyboard-manager/RNKeyboardManager.h +9 -0
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/keyboard-manager/RNKeyboardManager.mm +307 -0
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/package.json +8 -5
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/NativeDesktopShortcuts.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/api.ts +39 -14
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/NativeKeyboardManager.ts +12 -0
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/codes.ts +108 -0
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/index.ts +99 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/RNDesktopWindows.podspec +2 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/RNDesktopWindows.mm +229 -103
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/SparkWindowGeometry.h +63 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/LegendWindowRegistry.h +20 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/LegendWindowRegistry.mm +27 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManager.h +12 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManager.mm +3169 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManagerStartup.mm +31 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/SidebarStartupFallbacks.mm +14 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/package.json +10 -6
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/NativeDesktopWindowManager.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/api.ts +146 -70
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/diagnostics.ts +17 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/index.ts +3 -1
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/index.windows.ts +3 -1
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/macos-adapter.ts +53 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/macos.ts +51 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/transport.ts +41 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/types.ts +140 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/validation.ts +162 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/window-manager/NativeWindowManager.ts +28 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/WindowProvider.tsx +20 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/createWindowsNavigator.tsx +78 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/index.ts +6 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/primaryWindowLifecycle.ts +34 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/types.ts +11 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/usePrimaryWindowLifecycle.ts +18 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/useWindowFocusEffect.ts +17 -0
- package/vendor/node_modules/@legendapp/spark-documents/package.json +22 -0
- package/vendor/node_modules/@legendapp/spark-documents/src/controller.ts +97 -0
- package/vendor/node_modules/@legendapp/spark-documents/src/documentTransition.ts +13 -0
- package/vendor/node_modules/@legendapp/spark-documents/src/hooks.ts +139 -0
- package/vendor/node_modules/@legendapp/spark-documents/src/index.tsx +50 -0
- package/vendor/node_modules/@legendapp/spark-documents/src/reload.ts +39 -0
- package/vendor/node_modules/@legendapp/spark-documents/src/requests.ts +15 -0
- package/vendor/node_modules/@legendapp/spark-drag-drop/package.json +4 -3
- package/vendor/node_modules/@legendapp/spark-drag-drop/src/DesktopDragViewNativeComponent.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-drag-drop/src/contracts.ts +73 -18
- package/vendor/node_modules/@legendapp/spark-drag-drop/src/index.tsx +58 -15
- package/vendor/node_modules/@legendapp/spark-drag-drop/windows/SparkDragDrop/SparkDragDrop.h +14 -8
- package/vendor/node_modules/@legendapp/spark-file-dialog/ios/RNFileDialog.mm +34 -121
- package/vendor/node_modules/@legendapp/spark-file-dialog/package.json +12 -5
- package/vendor/node_modules/@legendapp/spark-file-dialog/src/NativeFileDialog.ts +1 -5
- package/vendor/node_modules/@legendapp/spark-file-dialog/src/index.ts +19 -84
- package/vendor/node_modules/@legendapp/spark-file-dialog/src/native.ts +12 -0
- package/vendor/node_modules/@legendapp/spark-file-dialog/src/options.ts +34 -14
- package/vendor/node_modules/@legendapp/spark-file-dialog/src/types.ts +26 -0
- package/vendor/node_modules/@legendapp/spark-file-dialog/windows/SparkFileDialog/SparkFileDialog.h +42 -58
- package/vendor/node_modules/@legendapp/spark-file-system/RNDesktopFileSystem.podspec +1 -0
- package/vendor/node_modules/@legendapp/spark-file-system/macos/RNDesktopFileSystem.mm +32 -21
- package/vendor/node_modules/@legendapp/spark-file-system/macos/SparkFileMutations.h +24 -0
- package/vendor/node_modules/@legendapp/spark-file-system/macos/SparkFileMutations.mm +215 -0
- package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScanner.h +9 -0
- package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScanner.mm +82 -0
- package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScannerCore.h +47 -0
- package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScannerCore.mm +306 -0
- package/vendor/node_modules/@legendapp/spark-file-system/package.json +8 -6
- package/vendor/node_modules/@legendapp/spark-file-system/src/NativeDesktopFileSystem.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-file-system/src/file-scanner/NativeFileScanner.ts +11 -0
- package/vendor/node_modules/@legendapp/spark-file-system/src/file-scanner/index.ts +93 -0
- package/vendor/node_modules/@legendapp/spark-file-system/src/handles.ts +48 -14
- package/vendor/node_modules/@legendapp/spark-file-system/src/index.ts +134 -45
- package/vendor/node_modules/@legendapp/spark-file-system/src/path.ts +1 -9
- package/vendor/node_modules/@legendapp/spark-file-system/windows/SparkFileSystem/SparkFileSystem.h +102 -26
- package/vendor/node_modules/@legendapp/spark-file-system/windows/SparkFileSystem/SparkFileSystem.vcxproj +2 -0
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/RNDesktopGlobalShortcuts.podspec +1 -0
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/macos/RNDesktopGlobalShortcuts.mm +7 -4
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/package.json +4 -3
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/NativeDesktopGlobalShortcuts.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/index.ts +39 -10
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/windows/SparkGlobalShortcuts/SparkGlobalShortcuts.h +5 -4
- package/vendor/node_modules/@legendapp/spark-message-dialog/macos/RNDesktopMessageDialog.mm +2 -2
- package/vendor/node_modules/@legendapp/spark-message-dialog/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-message-dialog/src/NativeDesktopMessageDialog.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-message-dialog/src/index.ts +48 -11
- package/vendor/node_modules/@legendapp/spark-native-menu/ios/RNNativeMenu.mm +141 -609
- package/vendor/node_modules/@legendapp/spark-native-menu/package.json +9 -4
- package/vendor/node_modules/@legendapp/spark-native-menu/src/NativeMenu.ts +2 -7
- package/vendor/node_modules/@legendapp/spark-native-menu/src/api.ts +76 -121
- package/vendor/node_modules/@legendapp/spark-native-menu/src/hooks.ts +97 -0
- package/vendor/node_modules/@legendapp/spark-native-menu/src/windows-menus.ts +43 -37
- package/vendor/node_modules/@legendapp/spark-notifications/macos/RNDesktopNotifications.mm +75 -30
- package/vendor/node_modules/@legendapp/spark-notifications/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-notifications/src/NativeDesktopNotifications.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-notifications/src/index.ts +130 -42
- package/vendor/node_modules/@legendapp/spark-notifications/windows/SparkNotifications/SparkNotifications.h +51 -8
- package/vendor/node_modules/@legendapp/spark-processes/macos/RNDesktopProcesses.mm +94 -21
- package/vendor/node_modules/@legendapp/spark-processes/package.json +4 -3
- package/vendor/node_modules/@legendapp/spark-processes/src/NativeDesktopProcesses.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-processes/src/index.ts +124 -28
- package/vendor/node_modules/@legendapp/spark-processes/src/types.ts +34 -0
- package/vendor/node_modules/@legendapp/spark-processes/windows/SparkProcesses/SparkProcesses.h +83 -34
- package/vendor/node_modules/@legendapp/spark-processes/windows/SparkProcesses/SparkProcesses.vcxproj +2 -0
- package/vendor/node_modules/@legendapp/spark-secure-storage/macos/RNDesktopSecureStorage.mm +49 -40
- package/vendor/node_modules/@legendapp/spark-secure-storage/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-secure-storage/src/NativeDesktopSecureStorage.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-secure-storage/src/desktop.ts +16 -14
- package/vendor/node_modules/@legendapp/spark-secure-storage/src/expo.ts +0 -1
- package/vendor/node_modules/@legendapp/spark-secure-storage/src/options.ts +3 -2
- package/vendor/node_modules/@legendapp/spark-secure-storage/src/unavailable.ts +6 -3
- package/vendor/node_modules/@legendapp/spark-settings/package.json +10 -3
- package/vendor/node_modules/@legendapp/spark-settings/src/fileStorage.ts +11 -0
- package/vendor/node_modules/@legendapp/spark-settings/src/index.ts +7 -9
- package/vendor/node_modules/@legendapp/spark-settings/src/storage/index.ts +142 -0
- package/vendor/node_modules/@legendapp/spark-settings/src/store.ts +63 -21
- package/vendor/node_modules/@legendapp/spark-settings-ui/package.json +20 -0
- package/vendor/node_modules/@legendapp/spark-settings-ui/src/index.tsx +436 -0
- package/vendor/node_modules/@legendapp/spark-settings-ui/src/options.ts +10 -0
- package/vendor/node_modules/@legendapp/spark-sqlite/package.json +3 -3
- package/vendor/node_modules/@legendapp/spark-sqlite/src/database.ts +182 -0
- package/vendor/node_modules/@legendapp/spark-sqlite/src/index.ts +19 -6
- package/vendor/node_modules/@legendapp/spark-sqlite/src/types.ts +22 -0
- package/vendor/node_modules/@legendapp/spark-system/macos/RNDesktopSystem.mm +36 -11
- package/vendor/node_modules/@legendapp/spark-system/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-system/src/NativeDesktopSystem.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-system/src/index.ts +89 -32
- package/vendor/node_modules/@legendapp/spark-system/windows/SparkSystem/SparkSystem.h +8 -4
- package/vendor/node_modules/@legendapp/spark-tray/macos/RNDesktopTray.mm +20 -8
- package/vendor/node_modules/@legendapp/spark-tray/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-tray/src/NativeDesktopTray.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-tray/src/index.ts +91 -34
- package/vendor/node_modules/@legendapp/spark-tray/windows/SparkTray/SparkTray.h +45 -15
- package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkButton.mm +2 -1
- package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkTextInput.mm +10 -2
- package/vendor/node_modules/@legendapp/spark-ui/macos/appkit-split-view/RNSidebarSplitViewComponent.h +15 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/appkit-split-view/RNSidebarSplitViewComponent.mm +1419 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/glass-effect-view/RNGlassEffectView.mm +72 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSegmentedControl.h +15 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSegmentedControl.mm +194 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSelect.h +15 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSelect.mm +180 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/sf-symbol/RNSFSymbol.h +15 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/sf-symbol/RNSFSymbol.mm +155 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/sidebar/RNSidebar.h +18 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/sidebar/RNSidebar.mm +645 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/swipe-actions/RNSwipeActions.h +10 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/swipe-actions/RNSwipeActions.mm +422 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/text-input-search/RNTextInputSearch.h +15 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/text-input-search/RNTextInputSearch.mm +164 -0
- package/vendor/node_modules/@legendapp/spark-ui/package.json +17 -4
- package/vendor/node_modules/@legendapp/spark-ui/src/SparkSelectNativeComponent.ts +3 -1
- package/vendor/node_modules/@legendapp/spark-ui/src/SparkTextInputNativeComponent.ts +6 -2
- package/vendor/node_modules/@legendapp/spark-ui/src/appkit-split-view/SidebarSplitViewNativeComponent.ts +37 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/appkit-split-view/index.tsx +86 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/availability.ts +10 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/classnames/index.ts +8 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/control.ts +42 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/desktop.tsx +56 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/glass-effect-view/index.tsx +15 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/index.android.tsx +35 -21
- package/vendor/node_modules/@legendapp/spark-ui/src/index.ios.tsx +55 -26
- package/vendor/node_modules/@legendapp/spark-ui/src/index.tsx +1 -27
- package/vendor/node_modules/@legendapp/spark-ui/src/index.web.tsx +45 -14
- package/vendor/node_modules/@legendapp/spark-ui/src/index.windows.tsx +1 -42
- package/vendor/node_modules/@legendapp/spark-ui/src/mobile-input.tsx +11 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/native-select/NativeSegmentedControlNativeComponent.ts +17 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/native-select/NativeSelectNativeComponent.ts +17 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/select.ts +5 -3
- package/vendor/node_modules/@legendapp/spark-ui/src/sf-symbol/SFSymbolNativeComponent.ts +13 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/sf-symbol/index.tsx +27 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/SidebarItemNativeComponent.ts +25 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/SidebarNativeComponent.ts +24 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/index.tsx +104 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/specialized.ts +22 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/swipe-actions/SwipeActionsNativeComponent.ts +11 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/swipe-actions/index.tsx +41 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/text-input-search/TextInputSearchNativeComponent.ts +33 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/text-input-search/index.tsx +34 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/types.ts +30 -19
- package/vendor/node_modules/@legendapp/spark-ui/uniwind.ts +3 -1
- package/vendor/node_modules/@legendapp/spark-ui/uniwind.web.ts +3 -1
- package/vendor/node_modules/@legendapp/spark-ui/windows/SparkUI/SparkUI.h +16 -11
- package/vendor/node_modules/@legendapp/spark-updates/macos/RNDesktopUpdates.mm +21 -8
- package/vendor/node_modules/@legendapp/spark-updates/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-updates/src/NativeDesktopUpdates.ts +3 -2
- package/vendor/node_modules/@legendapp/spark-updates/src/index.ts +71 -15
- package/vendor/node_modules/@legendapp/spark-webview/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-webview/src/WebView.tsx +11 -0
- package/vendor/node_modules/@legendapp/spark-webview/src/adapter.ts +55 -0
- package/vendor/node_modules/@legendapp/spark-webview/src/index.ts +2 -2
- package/vendor/node_modules/@legendapp/spark-webview/src/types.ts +32 -0
- package/vendor/node_modules/@legendapp/spark-webview/src/view-props.ts +28 -0
- package/vendor/node_modules/@legendapp/spark-window-options/index.cjs +51 -14
- package/vendor/node_modules/@legendapp/spark-window-options/index.d.ts +21 -8
- package/vendor/node_modules/@legendapp/spark-window-options/package.json +1 -1
- package/windows-macos.ts +1 -0
- package/expo-metro.cjs +0 -1
- package/message-dialog.ts +0 -1
- package/universal.cjs +0 -1
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/accelerator.ts +0 -22
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows-options.ts +0 -11
- package/vendor/node_modules/@legendapp/spark-file-dialog/src/index.windows.ts +0 -15
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/accelerator.ts +0 -22
- package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkSelect.h +0 -4
- package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkSelect.mm +0 -53
package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManager.mm
ADDED
|
@@ -0,0 +1,3169 @@
|
|
|
1
|
+
#include <math.h>
|
|
2
|
+
#import "RNWindowManager.h"
|
|
3
|
+
#import "LegendWindowRegistry.h"
|
|
4
|
+
#if TARGET_OS_OSX
|
|
5
|
+
extern "C" void SparkRebindWindowDelegate(NSWindow *, NSString *, id<NSWindowDelegate>);
|
|
6
|
+
#endif
|
|
7
|
+
|
|
8
|
+
#import <React-RCTAppDelegate/RCTRootViewFactory.h>
|
|
9
|
+
#import <React/RCTBridge.h>
|
|
10
|
+
#import <React/RCTBridgeModule.h>
|
|
11
|
+
#import <React/RCTRootView.h>
|
|
12
|
+
#import <React/RCTUIKit.h>
|
|
13
|
+
#import <React/RCTUtils.h>
|
|
14
|
+
#import <TargetConditionals.h>
|
|
15
|
+
|
|
16
|
+
#include <cmath>
|
|
17
|
+
#include <cxxreact/ReactMarker.h>
|
|
18
|
+
|
|
19
|
+
#if TARGET_OS_OSX && !__has_include(<RNDesktopApp/SparkDesktop.h>)
|
|
20
|
+
extern double LegendMainWindowFirstVisibleTimeMs;
|
|
21
|
+
extern double LegendMainWindowReactRootAttachedTimeMs;
|
|
22
|
+
#endif
|
|
23
|
+
|
|
24
|
+
#if TARGET_OS_OSX
|
|
25
|
+
#import <AppKit/AppKit.h>
|
|
26
|
+
#if __has_include(<RNDesktopApp/SparkDesktop.h>)
|
|
27
|
+
#import <RNDesktopApp/SparkDesktop.h>
|
|
28
|
+
#endif
|
|
29
|
+
#import <CoreImage/CoreImage.h>
|
|
30
|
+
#import <CoreGraphics/CoreGraphics.h>
|
|
31
|
+
#import <objc/runtime.h>
|
|
32
|
+
#import <QuartzCore/QuartzCore.h>
|
|
33
|
+
#endif
|
|
34
|
+
|
|
35
|
+
@protocol RNWindowManagerRootViewFactoryProvider <NSObject>
|
|
36
|
+
- (RCTRootViewFactory *)rootViewFactory;
|
|
37
|
+
#if TARGET_OS_OSX
|
|
38
|
+
- (NSWindow *)window;
|
|
39
|
+
#endif
|
|
40
|
+
@end
|
|
41
|
+
|
|
42
|
+
#if TARGET_OS_OSX
|
|
43
|
+
static char LegendManagedFullscreenPresentationKey;
|
|
44
|
+
static NSString * const LegendApplicationReopenRequestedNotification = @"LegendApplicationReopenRequestedNotification";
|
|
45
|
+
static NSString * const LegendMainWindowCloseRequestedNotification = @"LegendMainWindowCloseRequestedNotification";
|
|
46
|
+
extern "C" void LegendPrepareSidebarSplitViewStartup(NSWindow *, NSDictionary *) __attribute__((weak_import));
|
|
47
|
+
extern "C" BOOL LegendAttachSidebarSplitViewStartupRoot(NSWindow *, NSView *, void (^)(void)) __attribute__((weak_import));
|
|
48
|
+
extern "C" void LegendFinishSidebarSplitViewStartup(NSWindow *) __attribute__((weak_import));
|
|
49
|
+
static NSString * const LegendMainWindowStartupSplitViewKey = @"LegendMainWindowStartupSplitView";
|
|
50
|
+
|
|
51
|
+
extern "C" NSDictionary *LegendMainWindowStartupSplitViewConfiguration(void)
|
|
52
|
+
{
|
|
53
|
+
NSDictionary *configuration = [NSUserDefaults.standardUserDefaults dictionaryForKey:LegendMainWindowStartupSplitViewKey];
|
|
54
|
+
return [configuration[@"restoreOnLaunch"] isEqual:@NO] ? nil : configuration;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static NSString * const LegendRestorableWindowOptionsDefaultsKey = @"LegendRestorableWindowOptions";
|
|
58
|
+
static NSMutableDictionary<NSString *, NSWindow *> *LegendPrecreatedWindows;
|
|
59
|
+
static BOOL LegendApplicationIsTerminating = NO;
|
|
60
|
+
|
|
61
|
+
static NSString *LegendManagedWindowFrameAutosaveName(NSString *identifier)
|
|
62
|
+
{
|
|
63
|
+
NSString *bundleIdentifier = NSBundle.mainBundle.bundleIdentifier ?: @"legend-app";
|
|
64
|
+
return [NSString stringWithFormat:@"LegendManagedWindow.%@.%@", bundleIdentifier, identifier ?: @""];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
static NSArray<NSDictionary *> *LegendReadRestorableWindowOptions(void)
|
|
68
|
+
{
|
|
69
|
+
NSArray *records = [NSUserDefaults.standardUserDefaults arrayForKey:LegendRestorableWindowOptionsDefaultsKey];
|
|
70
|
+
if (![records isKindOfClass:NSArray.class]) {
|
|
71
|
+
return @[];
|
|
72
|
+
}
|
|
73
|
+
NSMutableArray<NSDictionary *> *options = [NSMutableArray new];
|
|
74
|
+
for (NSString *record in records) {
|
|
75
|
+
if (![record isKindOfClass:NSString.class]) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
NSData *data = [record dataUsingEncoding:NSUTF8StringEncoding];
|
|
79
|
+
id value = data ? [NSJSONSerialization JSONObjectWithData:data options:0 error:nil] : nil;
|
|
80
|
+
if ([value isKindOfClass:NSDictionary.class]) {
|
|
81
|
+
[options addObject:value];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return options;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
static NSDictionary *LegendFindRestorableWindowOptions(NSString *identifier)
|
|
88
|
+
{
|
|
89
|
+
for (NSDictionary *record in LegendReadRestorableWindowOptions()) {
|
|
90
|
+
if ([record[@"identifier"] isEqualToString:identifier]) {
|
|
91
|
+
return record;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return nil;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
static NSDictionary *LegendRestorableWindowOptions(NSDictionary *options)
|
|
98
|
+
{
|
|
99
|
+
static NSArray<NSString *> *keys;
|
|
100
|
+
static dispatch_once_t onceToken;
|
|
101
|
+
dispatch_once(&onceToken, ^{
|
|
102
|
+
keys = @[
|
|
103
|
+
@"deferOrderFront", @"hasShadow", @"height", @"identifier", @"level", @"moduleName",
|
|
104
|
+
@"representedURL", @"restoreOnLaunch", @"title", @"transparentBackground", @"width", @"windowStyle",
|
|
105
|
+
@"x", @"y",
|
|
106
|
+
];
|
|
107
|
+
});
|
|
108
|
+
NSMutableDictionary *restorableOptions = [NSMutableDictionary new];
|
|
109
|
+
for (NSString *key in keys) {
|
|
110
|
+
id value = options[key];
|
|
111
|
+
if (value != nil) {
|
|
112
|
+
restorableOptions[key] = value;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return restorableOptions;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
static void LegendWriteRestorableWindowOptions(NSArray<NSDictionary *> *options)
|
|
119
|
+
{
|
|
120
|
+
NSMutableArray<NSString *> *records = [NSMutableArray new];
|
|
121
|
+
for (NSDictionary *record in options) {
|
|
122
|
+
NSData *data = [NSJSONSerialization dataWithJSONObject:record options:0 error:nil];
|
|
123
|
+
NSString *json = data ? [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] : nil;
|
|
124
|
+
if (json) {
|
|
125
|
+
[records addObject:json];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
[NSUserDefaults.standardUserDefaults setObject:records forKey:LegendRestorableWindowOptionsDefaultsKey];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
static void LegendSetRestorableWindowOptions(NSString *identifier, NSDictionary *options)
|
|
132
|
+
{
|
|
133
|
+
if (identifier.length == 0) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
NSMutableArray<NSDictionary *> *records = [LegendReadRestorableWindowOptions() mutableCopy];
|
|
137
|
+
NSIndexSet *matchingIndexes = [records indexesOfObjectsPassingTest:^BOOL(NSDictionary *record, NSUInteger index, BOOL *stop) {
|
|
138
|
+
return [record[@"identifier"] isEqualToString:identifier];
|
|
139
|
+
}];
|
|
140
|
+
if (matchingIndexes.count > 0) {
|
|
141
|
+
[records removeObjectsAtIndexes:matchingIndexes];
|
|
142
|
+
}
|
|
143
|
+
if (options != nil) {
|
|
144
|
+
[records addObject:LegendRestorableWindowOptions(options)];
|
|
145
|
+
}
|
|
146
|
+
LegendWriteRestorableWindowOptions(records);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
static void LegendRemovePrecreatedWindow(NSString *identifier)
|
|
150
|
+
{
|
|
151
|
+
NSWindow *window = LegendPrecreatedWindows[identifier];
|
|
152
|
+
[LegendPrecreatedWindows removeObjectForKey:identifier];
|
|
153
|
+
[window close];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
extern "C" NSWindow *LegendTakePrecreatedWindow(NSString *identifier)
|
|
157
|
+
{
|
|
158
|
+
NSWindow *window = LegendPrecreatedWindows[identifier];
|
|
159
|
+
[LegendPrecreatedWindows removeObjectForKey:identifier];
|
|
160
|
+
return window;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
static inline NSAppearance *LegendDarkAppearance()
|
|
164
|
+
{
|
|
165
|
+
if (@available(macOS 10.14, *)) {
|
|
166
|
+
return [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua];
|
|
167
|
+
} else if (@available(macOS 10.10, *)) {
|
|
168
|
+
return [NSAppearance appearanceNamed:NSAppearanceNameVibrantDark];
|
|
169
|
+
}
|
|
170
|
+
return nil;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
static NSAppearance *LegendAppearanceForName(NSString *value)
|
|
176
|
+
{
|
|
177
|
+
if (![value isKindOfClass:NSString.class] || value.length == 0 || [value isEqualToString:@"system"]) {
|
|
178
|
+
return nil;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if ([value isEqualToString:@"light"]) {
|
|
182
|
+
return [NSAppearance appearanceNamed:NSAppearanceNameAqua];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if ([value isEqualToString:@"dark"]) {
|
|
186
|
+
return [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua] ?: LegendDarkAppearance();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return nil;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
static void LegendApplyWindowAppearance(NSWindow *window, NSString *value)
|
|
193
|
+
{
|
|
194
|
+
if (![value isKindOfClass:NSString.class] || value.length == 0) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
window.appearance = LegendAppearanceForName(value);
|
|
199
|
+
[window displayIfNeeded];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
static void LegendApplyTitleVisibility(NSWindow *window, NSString *value)
|
|
203
|
+
{
|
|
204
|
+
if (![value isKindOfClass:NSString.class] || value.length == 0) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if ([value isEqualToString:@"hidden"]) {
|
|
209
|
+
window.titleVisibility = NSWindowTitleHidden;
|
|
210
|
+
} else if ([value isEqualToString:@"visible"]) {
|
|
211
|
+
window.titleVisibility = NSWindowTitleVisible;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
static void LegendApplyToolbarStyle(NSWindow *window, NSString *value)
|
|
216
|
+
{
|
|
217
|
+
if (![value isKindOfClass:NSString.class] || value.length == 0) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (@available(macOS 11.0, *)) {
|
|
222
|
+
if ([value isEqualToString:@"automatic"]) {
|
|
223
|
+
window.toolbarStyle = NSWindowToolbarStyleAutomatic;
|
|
224
|
+
} else if ([value isEqualToString:@"expanded"]) {
|
|
225
|
+
window.toolbarStyle = NSWindowToolbarStyleExpanded;
|
|
226
|
+
} else if ([value isEqualToString:@"preference"]) {
|
|
227
|
+
window.toolbarStyle = NSWindowToolbarStylePreference;
|
|
228
|
+
} else if ([value isEqualToString:@"unified"]) {
|
|
229
|
+
window.toolbarStyle = NSWindowToolbarStyleUnified;
|
|
230
|
+
} else if ([value isEqualToString:@"unifiedCompact"]) {
|
|
231
|
+
window.toolbarStyle = NSWindowToolbarStyleUnifiedCompact;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
static void LegendApplyTitlebarSeparatorStyle(NSWindow *window, NSString *value)
|
|
237
|
+
{
|
|
238
|
+
if (![value isKindOfClass:NSString.class] || value.length == 0) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (@available(macOS 11.0, *)) {
|
|
243
|
+
if ([value isEqualToString:@"automatic"]) {
|
|
244
|
+
window.titlebarSeparatorStyle = NSTitlebarSeparatorStyleAutomatic;
|
|
245
|
+
} else if ([value isEqualToString:@"none"]) {
|
|
246
|
+
window.titlebarSeparatorStyle = NSTitlebarSeparatorStyleNone;
|
|
247
|
+
} else if ([value isEqualToString:@"line"]) {
|
|
248
|
+
window.titlebarSeparatorStyle = NSTitlebarSeparatorStyleLine;
|
|
249
|
+
} else if ([value isEqualToString:@"shadow"]) {
|
|
250
|
+
window.titlebarSeparatorStyle = NSTitlebarSeparatorStyleShadow;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
static NSColor *LegendColorFromHexString(NSString *value)
|
|
256
|
+
{
|
|
257
|
+
if (![value isKindOfClass:NSString.class] || value.length == 0) {
|
|
258
|
+
return nil;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if ([value isEqualToString:@"system"]) {
|
|
262
|
+
return NSColor.windowBackgroundColor;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
NSString *hex = [value hasPrefix:@"#"] ? [value substringFromIndex:1] : value;
|
|
266
|
+
if (hex.length != 6 && hex.length != 8) {
|
|
267
|
+
return nil;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
unsigned long long raw = 0;
|
|
271
|
+
NSScanner *scanner = [NSScanner scannerWithString:hex];
|
|
272
|
+
if (![scanner scanHexLongLong:&raw]) {
|
|
273
|
+
return nil;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
CGFloat red = 0;
|
|
277
|
+
CGFloat green = 0;
|
|
278
|
+
CGFloat blue = 0;
|
|
279
|
+
CGFloat alpha = 1;
|
|
280
|
+
|
|
281
|
+
if (hex.length == 8) {
|
|
282
|
+
red = ((raw >> 24) & 0xff) / 255.0;
|
|
283
|
+
green = ((raw >> 16) & 0xff) / 255.0;
|
|
284
|
+
blue = ((raw >> 8) & 0xff) / 255.0;
|
|
285
|
+
alpha = (raw & 0xff) / 255.0;
|
|
286
|
+
} else {
|
|
287
|
+
red = ((raw >> 16) & 0xff) / 255.0;
|
|
288
|
+
green = ((raw >> 8) & 0xff) / 255.0;
|
|
289
|
+
blue = (raw & 0xff) / 255.0;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return [NSColor colorWithSRGBRed:red green:green blue:blue alpha:alpha];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
static BOOL LegendAppearanceIsDark(NSAppearance *appearance)
|
|
296
|
+
{
|
|
297
|
+
if (@available(macOS 10.14, *)) {
|
|
298
|
+
NSAppearanceName match = [appearance bestMatchFromAppearancesWithNames:@[
|
|
299
|
+
NSAppearanceNameDarkAqua,
|
|
300
|
+
NSAppearanceNameAqua,
|
|
301
|
+
]];
|
|
302
|
+
return [match isEqualToString:NSAppearanceNameDarkAqua];
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return NO;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
static BOOL LegendWindowUsesDarkAppearance(NSWindow *window)
|
|
309
|
+
{
|
|
310
|
+
if (window.appearance != nil) {
|
|
311
|
+
return LegendAppearanceIsDark(window.appearance);
|
|
312
|
+
}
|
|
313
|
+
if (NSApp.appearance != nil) {
|
|
314
|
+
return LegendAppearanceIsDark(NSApp.appearance);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
NSString *interfaceStyle = [NSUserDefaults.standardUserDefaults stringForKey:@"AppleInterfaceStyle"];
|
|
318
|
+
return [interfaceStyle caseInsensitiveCompare:@"Dark"] == NSOrderedSame ||
|
|
319
|
+
LegendAppearanceIsDark(NSApp.effectiveAppearance);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
static NSColor *LegendWindowStartupBackgroundColor(NSWindow *window, NSString *value)
|
|
323
|
+
{
|
|
324
|
+
NSColor *configuredColor = LegendColorFromHexString(value);
|
|
325
|
+
if (configuredColor != nil) {
|
|
326
|
+
return configuredColor;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return LegendWindowUsesDarkAppearance(window)
|
|
330
|
+
? [NSColor colorWithSRGBRed:25.0 / 255.0 green:26.0 / 255.0 blue:27.0 / 255.0 alpha:1]
|
|
331
|
+
: [NSColor colorWithSRGBRed:245.0 / 255.0 green:246.0 / 255.0 blue:248.0 / 255.0 alpha:1];
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
static void LegendApplyBackgroundColorToView(NSView *view, NSColor *backgroundColor)
|
|
335
|
+
{
|
|
336
|
+
if (!view || !backgroundColor) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if ([view respondsToSelector:@selector(setBackgroundColor:)]) {
|
|
341
|
+
[(id)view setBackgroundColor:backgroundColor];
|
|
342
|
+
}
|
|
343
|
+
view.wantsLayer = YES;
|
|
344
|
+
view.layer.backgroundColor = backgroundColor.CGColor;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
static BOOL LegendViewIsTitlebarContainer(NSView *view)
|
|
348
|
+
{
|
|
349
|
+
NSString *className = NSStringFromClass(view.class);
|
|
350
|
+
return [className isEqualToString:@"NSTitlebarContainerView"] ||
|
|
351
|
+
[className isEqualToString:@"NSTitlebarView"] ||
|
|
352
|
+
[className isEqualToString:@"NSTitlebarBackgroundView"];
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
static NSVisualEffectMaterial LegendVisualEffectMaterialForTitlebarMaterial(NSString *value)
|
|
356
|
+
{
|
|
357
|
+
if ([value isEqualToString:@"hudWindow"]) {
|
|
358
|
+
return NSVisualEffectMaterialHUDWindow;
|
|
359
|
+
}
|
|
360
|
+
if ([value isEqualToString:@"sidebar"]) {
|
|
361
|
+
return NSVisualEffectMaterialSidebar;
|
|
362
|
+
}
|
|
363
|
+
if ([value isEqualToString:@"windowBackground"]) {
|
|
364
|
+
return NSVisualEffectMaterialWindowBackground;
|
|
365
|
+
}
|
|
366
|
+
if ([value isEqualToString:@"titlebar"]) {
|
|
367
|
+
return NSVisualEffectMaterialTitlebar;
|
|
368
|
+
}
|
|
369
|
+
if ([value isEqualToString:@"glass"] || [value isEqualToString:@"headerView"]) {
|
|
370
|
+
if (@available(macOS 10.14, *)) {
|
|
371
|
+
return NSVisualEffectMaterialHeaderView;
|
|
372
|
+
}
|
|
373
|
+
return NSVisualEffectMaterialTitlebar;
|
|
374
|
+
}
|
|
375
|
+
return NSVisualEffectMaterialTitlebar;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
static NSVisualEffectBlendingMode LegendVisualEffectBlendingModeForName(NSString *value)
|
|
379
|
+
{
|
|
380
|
+
return [value isEqualToString:@"withinWindow"] ? NSVisualEffectBlendingModeWithinWindow : NSVisualEffectBlendingModeBehindWindow;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
static NSVisualEffectState LegendVisualEffectStateForName(NSString *value)
|
|
384
|
+
{
|
|
385
|
+
if ([value isEqualToString:@"active"]) {
|
|
386
|
+
return NSVisualEffectStateActive;
|
|
387
|
+
}
|
|
388
|
+
if ([value isEqualToString:@"inactive"]) {
|
|
389
|
+
return NSVisualEffectStateInactive;
|
|
390
|
+
}
|
|
391
|
+
return NSVisualEffectStateFollowsWindowActiveState;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
static NSRect LegendTitlebarMaterialFrame(NSWindow *window, NSView *frameView)
|
|
395
|
+
{
|
|
396
|
+
NSRect frameBounds = frameView.bounds;
|
|
397
|
+
NSRect contentLayoutRect = [frameView convertRect:window.contentLayoutRect fromView:nil];
|
|
398
|
+
CGFloat materialMinY = NSMaxY(contentLayoutRect);
|
|
399
|
+
CGFloat materialHeight = NSMaxY(frameBounds) - materialMinY;
|
|
400
|
+
|
|
401
|
+
if (materialHeight <= 0 || materialHeight > NSHeight(frameBounds)) {
|
|
402
|
+
materialHeight = MAX(0, NSHeight(window.frame) - NSHeight(window.contentLayoutRect));
|
|
403
|
+
materialMinY = NSMaxY(frameBounds) - materialHeight;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return NSMakeRect(0, materialMinY, NSWidth(frameBounds), materialHeight);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
API_AVAILABLE(macos(26.0))
|
|
410
|
+
static NSView *LegendCreateOverscannedGlassEffectView(NSRect frame)
|
|
411
|
+
{
|
|
412
|
+
NSView *containerView = [[NSView alloc] initWithFrame:frame];
|
|
413
|
+
containerView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
|
414
|
+
containerView.wantsLayer = YES;
|
|
415
|
+
containerView.layer.backgroundColor = NSColor.clearColor.CGColor;
|
|
416
|
+
containerView.layer.masksToBounds = YES;
|
|
417
|
+
|
|
418
|
+
CGFloat overscan = 48;
|
|
419
|
+
NSRect glassFrame = NSMakeRect(-overscan, 0, NSWidth(frame) + overscan, NSHeight(frame) + overscan);
|
|
420
|
+
NSGlassEffectView *glassView = [[NSGlassEffectView alloc] initWithFrame:glassFrame];
|
|
421
|
+
glassView.cornerRadius = 0;
|
|
422
|
+
glassView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
|
423
|
+
glassView.wantsLayer = YES;
|
|
424
|
+
glassView.layer.backgroundColor = NSColor.clearColor.CGColor;
|
|
425
|
+
[containerView addSubview:glassView];
|
|
426
|
+
|
|
427
|
+
return containerView;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
static void LegendApplyTitlebarBackgroundColor(NSView *view, NSColor *backgroundColor)
|
|
431
|
+
{
|
|
432
|
+
BOOL isTitlebarContainer = LegendViewIsTitlebarContainer(view);
|
|
433
|
+
|
|
434
|
+
if (isTitlebarContainer) {
|
|
435
|
+
LegendApplyBackgroundColorToView(view, backgroundColor);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
for (NSView *subview in view.subviews) {
|
|
439
|
+
LegendApplyTitlebarBackgroundColor(subview, backgroundColor);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
static BOOL LegendUsesFullSizeContentLayout(NSWindow *window);
|
|
444
|
+
|
|
445
|
+
static void LegendApplyWindowBackgroundColor(NSWindow *window, NSString *value)
|
|
446
|
+
{
|
|
447
|
+
NSColor *backgroundColor = LegendColorFromHexString(value);
|
|
448
|
+
if (!backgroundColor) {
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
window.backgroundColor = backgroundColor;
|
|
453
|
+
window.opaque = backgroundColor.alphaComponent >= 1;
|
|
454
|
+
|
|
455
|
+
NSView *contentView = window.contentView;
|
|
456
|
+
if (contentView) {
|
|
457
|
+
// System window backing must remain behind AppKit-managed sidebar materials.
|
|
458
|
+
LegendApplyBackgroundColorToView(contentView, [value isEqualToString:@"system"] ? NSColor.clearColor : backgroundColor);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
NSView *frameView = contentView.superview;
|
|
462
|
+
if (frameView) {
|
|
463
|
+
NSColor *titlebarBackgroundColor = LegendUsesFullSizeContentLayout(window) ? NSColor.clearColor : backgroundColor;
|
|
464
|
+
LegendApplyTitlebarBackgroundColor(frameView, titlebarBackgroundColor);
|
|
465
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
466
|
+
NSView *currentFrameView = window.contentView.superview;
|
|
467
|
+
if (currentFrameView) {
|
|
468
|
+
LegendApplyTitlebarBackgroundColor(currentFrameView, titlebarBackgroundColor);
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
static char LegendManagedRootViewKey;
|
|
475
|
+
static char LegendContentLayoutModeKey;
|
|
476
|
+
static char LegendTitlebarControlMetadataKey;
|
|
477
|
+
static char LegendToolbarControlMetadataKey;
|
|
478
|
+
static char LegendToolbarWindowIdentifierKey;
|
|
479
|
+
static char LegendToolbarSearchFieldKey;
|
|
480
|
+
static char LegendToolbarSearchItemKey;
|
|
481
|
+
static char LegendToolbarSearchWidthKey;
|
|
482
|
+
static char LegendToolbarSearchButtonKey;
|
|
483
|
+
static char LegendToolbarSearchContainerKey;
|
|
484
|
+
static char LegendToolbarSearchWidthConstraintKey;
|
|
485
|
+
static const CGFloat LegendToolbarSearchCollapsedSize = 34;
|
|
486
|
+
static const CGFloat LegendToolbarIconControlWidth = 36;
|
|
487
|
+
|
|
488
|
+
// AppKit synchronizes all toolbars with the same identifier as a family.
|
|
489
|
+
// Our windows own independent item state, including across React reloads.
|
|
490
|
+
static NSToolbar *LegendCreateToolbar(NSString *windowIdentifier)
|
|
491
|
+
{
|
|
492
|
+
NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:
|
|
493
|
+
[@"LegendToolbar." stringByAppendingString:NSUUID.UUID.UUIDString]];
|
|
494
|
+
objc_setAssociatedObject(toolbar, &LegendToolbarWindowIdentifierKey,
|
|
495
|
+
windowIdentifier ?: @"", OBJC_ASSOCIATION_COPY_NONATOMIC);
|
|
496
|
+
toolbar.displayMode = NSToolbarDisplayModeIconOnly;
|
|
497
|
+
toolbar.showsBaselineSeparator = NO;
|
|
498
|
+
return toolbar;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
static NSString *LegendToolbarWindowIdentifier(NSToolbar *toolbar)
|
|
502
|
+
{
|
|
503
|
+
return objc_getAssociatedObject(toolbar, &LegendToolbarWindowIdentifierKey) ?: @"";
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
static RCTUIView *LegendManagedRootView(NSWindow *window)
|
|
507
|
+
{
|
|
508
|
+
id associatedRootView = objc_getAssociatedObject(window, &LegendManagedRootViewKey);
|
|
509
|
+
if ([associatedRootView isKindOfClass:RCTUIView.class]) {
|
|
510
|
+
return associatedRootView;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if ([window.contentView isKindOfClass:RCTUIView.class]) {
|
|
514
|
+
return (RCTUIView *)window.contentView;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return nil;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
static void LegendEnsureRootViewContainer(NSWindow *window, RCTUIView *rootView)
|
|
521
|
+
{
|
|
522
|
+
if (!window || !rootView || window.contentView != rootView) {
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
NSView *containerView = [[NSView alloc] initWithFrame:rootView.frame];
|
|
527
|
+
containerView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
|
528
|
+
LegendApplyBackgroundColorToView(containerView, window.backgroundColor);
|
|
529
|
+
|
|
530
|
+
window.contentView = containerView;
|
|
531
|
+
[containerView addSubview:rootView];
|
|
532
|
+
objc_setAssociatedObject(window, &LegendManagedRootViewKey, rootView, OBJC_ASSOCIATION_ASSIGN);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
static void LegendApplyContentLayoutModeOption(NSWindow *window, NSString *contentLayoutMode)
|
|
536
|
+
{
|
|
537
|
+
if (!window || ![contentLayoutMode isKindOfClass:NSString.class]) {
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
objc_setAssociatedObject(window, &LegendContentLayoutModeKey, contentLayoutMode, OBJC_ASSOCIATION_COPY_NONATOMIC);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
static BOOL LegendUsesFullSizeContentLayout(NSWindow *window)
|
|
545
|
+
{
|
|
546
|
+
id contentLayoutMode = objc_getAssociatedObject(window, &LegendContentLayoutModeKey);
|
|
547
|
+
return [contentLayoutMode isKindOfClass:NSString.class] && [(NSString *)contentLayoutMode isEqualToString:@"fullSize"];
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
static void LegendPrepareWindowForDisplay(NSWindow *window, NSString *backgroundColor)
|
|
551
|
+
{
|
|
552
|
+
LegendApplyWindowBackgroundColor(window, backgroundColor);
|
|
553
|
+
[window.contentView displayIfNeeded];
|
|
554
|
+
[window displayIfNeeded];
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
static void LegendApplyContentLayoutMode(NSWindow *window, NSNumber *maskNumber, BOOL usesTitlebarBackground)
|
|
558
|
+
{
|
|
559
|
+
if (usesTitlebarBackground) {
|
|
560
|
+
window.styleMask = window.styleMask | NSWindowStyleMaskFullSizeContentView;
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (maskNumber && (maskNumber.unsignedIntegerValue & NSWindowStyleMaskFullSizeContentView) == 0) {
|
|
565
|
+
window.styleMask = window.styleMask & ~NSWindowStyleMaskFullSizeContentView;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
static BOOL LegendDictionaryHasKey(NSDictionary *dictionary, NSString *key)
|
|
570
|
+
{
|
|
571
|
+
return [dictionary isKindOfClass:NSDictionary.class] && dictionary[key] != nil;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
static void LegendSizeRootViewToWindow(RCTUIView *rootView, NSWindow *window);
|
|
575
|
+
|
|
576
|
+
static NSURL *LegendURLFromString(NSString *value)
|
|
577
|
+
{
|
|
578
|
+
if (![value isKindOfClass:NSString.class] || value.length == 0) {
|
|
579
|
+
return nil;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
NSURL *url = [NSURL URLWithString:value];
|
|
583
|
+
if (url.scheme.length > 0) {
|
|
584
|
+
return url;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
return [NSURL fileURLWithPath:value];
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
static void LegendApplyWindowTitleAndRepresentedURL(NSWindow *window,
|
|
591
|
+
NSString *title,
|
|
592
|
+
BOOL hasRepresentedURL,
|
|
593
|
+
id representedURLValue,
|
|
594
|
+
NSString *fallbackTitle)
|
|
595
|
+
{
|
|
596
|
+
NSURL *representedURL = [representedURLValue isKindOfClass:NSString.class]
|
|
597
|
+
? LegendURLFromString((NSString *)representedURLValue)
|
|
598
|
+
: nil;
|
|
599
|
+
|
|
600
|
+
if (hasRepresentedURL) {
|
|
601
|
+
if ([representedURLValue isKindOfClass:NSNull.class]) {
|
|
602
|
+
window.representedURL = nil;
|
|
603
|
+
} else if (representedURL.fileURL && representedURL.path.length > 0) {
|
|
604
|
+
window.representedURL = representedURL;
|
|
605
|
+
} else {
|
|
606
|
+
window.representedURL = representedURL;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
if (!title && representedURL.lastPathComponent.length > 0) {
|
|
611
|
+
title = representedURL.lastPathComponent;
|
|
612
|
+
}
|
|
613
|
+
window.title = title ?: fallbackTitle ?: @"";
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
static void LegendApplyWindowOptions(NSWindow *window, NSDictionary *options)
|
|
617
|
+
{
|
|
618
|
+
if (!window || ![options isKindOfClass:NSDictionary.class]) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
BOOL hasTitle = LegendDictionaryHasKey(options, @"title");
|
|
623
|
+
BOOL hasRepresentedURL = LegendDictionaryHasKey(options, @"representedURL");
|
|
624
|
+
id representedURLValue = hasRepresentedURL ? options[@"representedURL"] : nil;
|
|
625
|
+
NSString *title = [options[@"title"] isKindOfClass:NSString.class] ? options[@"title"] : nil;
|
|
626
|
+
if (hasTitle || hasRepresentedURL) {
|
|
627
|
+
LegendApplyWindowTitleAndRepresentedURL(window, title, hasRepresentedURL, representedURLValue, nil);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
NSDictionary *windowStyle = [options[@"windowStyle"] isKindOfClass:NSDictionary.class] ? options[@"windowStyle"] : @{};
|
|
631
|
+
if (windowStyle[@"startupSplitView"] == NSNull.null && LegendFinishSidebarSplitViewStartup) {
|
|
632
|
+
LegendFinishSidebarSplitViewStartup(window);
|
|
633
|
+
}
|
|
634
|
+
NSNumber *maskNumber = [windowStyle[@"mask"] isKindOfClass:NSNumber.class] ? windowStyle[@"mask"] : nil;
|
|
635
|
+
NSNumber *transparentTitlebar = [windowStyle[@"titlebarAppearsTransparent"] isKindOfClass:NSNumber.class]
|
|
636
|
+
? windowStyle[@"titlebarAppearsTransparent"]
|
|
637
|
+
: nil;
|
|
638
|
+
NSString *titleVisibility = [windowStyle[@"titleVisibility"] isKindOfClass:NSString.class]
|
|
639
|
+
? windowStyle[@"titleVisibility"]
|
|
640
|
+
: nil;
|
|
641
|
+
NSString *toolbarStyle = [windowStyle[@"toolbarStyle"] isKindOfClass:NSString.class] ? windowStyle[@"toolbarStyle"] : nil;
|
|
642
|
+
NSString *contentLayoutMode = [windowStyle[@"contentLayoutMode"] isKindOfClass:NSString.class]
|
|
643
|
+
? windowStyle[@"contentLayoutMode"]
|
|
644
|
+
: nil;
|
|
645
|
+
NSString *titlebarSeparatorStyle = [windowStyle[@"titlebarSeparatorStyle"] isKindOfClass:NSString.class]
|
|
646
|
+
? windowStyle[@"titlebarSeparatorStyle"]
|
|
647
|
+
: nil;
|
|
648
|
+
NSString *backgroundColor = [windowStyle[@"backgroundColor"] isKindOfClass:NSString.class]
|
|
649
|
+
? windowStyle[@"backgroundColor"]
|
|
650
|
+
: nil;
|
|
651
|
+
NSString *appearance = [windowStyle[@"appearance"] isKindOfClass:NSString.class]
|
|
652
|
+
? windowStyle[@"appearance"]
|
|
653
|
+
: nil;
|
|
654
|
+
BOOL hasToolbarKey = LegendDictionaryHasKey(windowStyle, @"hasToolbar");
|
|
655
|
+
BOOL hasToolbar = [windowStyle[@"hasToolbar"] boolValue];
|
|
656
|
+
BOOL usesTitlebarBackground = transparentTitlebar.boolValue && backgroundColor.length > 0;
|
|
657
|
+
|
|
658
|
+
if (maskNumber) window.styleMask = maskNumber.unsignedIntegerValue;
|
|
659
|
+
if (options[@"level"]) window.level = [options[@"level"] integerValue];
|
|
660
|
+
if (windowStyle[@"trafficLights"]) for (NSNumber *button in @[@(NSWindowCloseButton), @(NSWindowMiniaturizeButton), @(NSWindowZoomButton)]) [window standardWindowButton:(NSWindowButton)button.integerValue].hidden = ![windowStyle[@"trafficLights"] boolValue];
|
|
661
|
+
if (windowStyle[@"panelStyle"]) {
|
|
662
|
+
NSWindowStyleMask mask = window.styleMask & ~(NSWindowStyleMaskUtilityWindow | NSWindowStyleMaskDocModalWindow | NSWindowStyleMaskNonactivatingPanel);
|
|
663
|
+
NSString *panel = windowStyle[@"panelStyle"];
|
|
664
|
+
window.styleMask = mask | ([panel isEqual:@"utility"] ? NSWindowStyleMaskUtilityWindow : [panel isEqual:@"documentModal"] ? NSWindowStyleMaskDocModalWindow : NSWindowStyleMaskNonactivatingPanel);
|
|
665
|
+
}
|
|
666
|
+
LegendApplyContentLayoutModeOption(window, contentLayoutMode);
|
|
667
|
+
LegendApplyContentLayoutMode(window, maskNumber, usesTitlebarBackground);
|
|
668
|
+
if (transparentTitlebar != nil) {
|
|
669
|
+
window.titlebarAppearsTransparent = transparentTitlebar.boolValue;
|
|
670
|
+
}
|
|
671
|
+
LegendApplyTitleVisibility(window, titleVisibility);
|
|
672
|
+
if (hasToolbarKey) {
|
|
673
|
+
if (hasToolbar && !window.toolbar) {
|
|
674
|
+
NSToolbar *toolbar = LegendCreateToolbar([options[@"identifier"] isKindOfClass:NSString.class] ? options[@"identifier"] : @"main");
|
|
675
|
+
window.toolbar = toolbar;
|
|
676
|
+
} else if (!hasToolbar) {
|
|
677
|
+
window.toolbar = nil;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
LegendApplyToolbarStyle(window, toolbarStyle);
|
|
681
|
+
LegendApplyTitlebarSeparatorStyle(window, titlebarSeparatorStyle);
|
|
682
|
+
LegendApplyWindowAppearance(window, appearance);
|
|
683
|
+
LegendApplyWindowBackgroundColor(window, backgroundColor);
|
|
684
|
+
|
|
685
|
+
RCTUIView *rootView = LegendManagedRootView(window);
|
|
686
|
+
if (usesTitlebarBackground && rootView) {
|
|
687
|
+
LegendEnsureRootViewContainer(window, rootView);
|
|
688
|
+
LegendSizeRootViewToWindow(rootView, window);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
static void LegendSizeRootViewToWindow(RCTUIView *rootView, NSWindow *window)
|
|
693
|
+
{
|
|
694
|
+
if (!rootView || !window) {
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
NSView *contentView = window.contentView;
|
|
699
|
+
if (contentView && contentView != rootView && (window.styleMask & NSWindowStyleMaskFullSizeContentView) != 0) {
|
|
700
|
+
rootView.frame = LegendUsesFullSizeContentLayout(window)
|
|
701
|
+
? contentView.bounds
|
|
702
|
+
: [contentView convertRect:window.contentLayoutRect fromView:nil];
|
|
703
|
+
} else {
|
|
704
|
+
rootView.frame = contentView ? contentView.bounds : NSMakeRect(0, 0, window.frame.size.width, window.frame.size.height);
|
|
705
|
+
}
|
|
706
|
+
rootView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
|
707
|
+
[rootView setNeedsLayout:YES];
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
extern "C" void LegendPrecreateRestorableWindows(void)
|
|
711
|
+
{
|
|
712
|
+
if (LegendPrecreatedWindows != nil) {
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
LegendPrecreatedWindows = [NSMutableDictionary new];
|
|
717
|
+
for (NSDictionary *options in LegendReadRestorableWindowOptions()) {
|
|
718
|
+
NSString *identifier = [options[@"identifier"] isKindOfClass:NSString.class] ? options[@"identifier"] : nil;
|
|
719
|
+
if (identifier.length == 0 || ![options[@"restoreOnLaunch"] boolValue]) {
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
NSDictionary *windowStyle = [options[@"windowStyle"] isKindOfClass:NSDictionary.class]
|
|
724
|
+
? options[@"windowStyle"]
|
|
725
|
+
: @{};
|
|
726
|
+
NSNumber *maskNumber = [windowStyle[@"mask"] isKindOfClass:NSNumber.class] ? windowStyle[@"mask"] : nil;
|
|
727
|
+
NSNumber *widthNumber = [windowStyle[@"width"] isKindOfClass:NSNumber.class]
|
|
728
|
+
? windowStyle[@"width"]
|
|
729
|
+
: ([options[@"width"] isKindOfClass:NSNumber.class] ? options[@"width"] : nil);
|
|
730
|
+
NSNumber *heightNumber = [windowStyle[@"height"] isKindOfClass:NSNumber.class]
|
|
731
|
+
? windowStyle[@"height"]
|
|
732
|
+
: ([options[@"height"] isKindOfClass:NSNumber.class] ? options[@"height"] : nil);
|
|
733
|
+
CGFloat width = widthNumber ? MAX(widthNumber.doubleValue, 1) : 400;
|
|
734
|
+
CGFloat height = heightNumber ? MAX(heightNumber.doubleValue, 1) : 300;
|
|
735
|
+
NSUInteger styleMask = maskNumber
|
|
736
|
+
? maskNumber.unsignedIntegerValue
|
|
737
|
+
: (NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable);
|
|
738
|
+
NSWindow *window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, width, height)
|
|
739
|
+
styleMask:styleMask
|
|
740
|
+
backing:NSBackingStoreBuffered
|
|
741
|
+
defer:NO];
|
|
742
|
+
window.releasedWhenClosed = NO;
|
|
743
|
+
LegendApplyWindowOptions(window, options);
|
|
744
|
+
|
|
745
|
+
NSNumber *levelNumber = [options[@"level"] isKindOfClass:NSNumber.class] ? options[@"level"] : nil;
|
|
746
|
+
if (levelNumber) {
|
|
747
|
+
window.level = levelNumber.integerValue;
|
|
748
|
+
}
|
|
749
|
+
NSNumber *hasShadow = [options[@"hasShadow"] isKindOfClass:NSNumber.class] ? options[@"hasShadow"] : nil;
|
|
750
|
+
if (hasShadow) {
|
|
751
|
+
window.hasShadow = hasShadow.boolValue;
|
|
752
|
+
}
|
|
753
|
+
BOOL transparentBackground = [options[@"transparentBackground"] boolValue];
|
|
754
|
+
NSString *backgroundColor = [windowStyle[@"backgroundColor"] isKindOfClass:NSString.class]
|
|
755
|
+
? windowStyle[@"backgroundColor"]
|
|
756
|
+
: nil;
|
|
757
|
+
NSColor *startupBackgroundColor = transparentBackground
|
|
758
|
+
? NSColor.clearColor
|
|
759
|
+
: LegendWindowStartupBackgroundColor(window, backgroundColor);
|
|
760
|
+
window.backgroundColor = startupBackgroundColor;
|
|
761
|
+
window.opaque = !transparentBackground && startupBackgroundColor.alphaComponent >= 1;
|
|
762
|
+
|
|
763
|
+
NSView *placeholder = [[NSView alloc] initWithFrame:window.contentView.bounds];
|
|
764
|
+
placeholder.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
|
765
|
+
LegendApplyBackgroundColorToView(placeholder, startupBackgroundColor);
|
|
766
|
+
window.contentView = placeholder;
|
|
767
|
+
|
|
768
|
+
NSString *autosaveName = LegendManagedWindowFrameAutosaveName(identifier);
|
|
769
|
+
[window setFrameAutosaveName:autosaveName];
|
|
770
|
+
if (![window setFrameUsingName:autosaveName]) {
|
|
771
|
+
NSNumber *originX = [options[@"x"] isKindOfClass:NSNumber.class] ? options[@"x"] : nil;
|
|
772
|
+
NSNumber *originY = [options[@"y"] isKindOfClass:NSNumber.class] ? options[@"y"] : nil;
|
|
773
|
+
if (originX || originY) {
|
|
774
|
+
NSPoint origin = window.frame.origin;
|
|
775
|
+
origin.x = originX ? originX.doubleValue : origin.x;
|
|
776
|
+
origin.y = originY ? originY.doubleValue : origin.y;
|
|
777
|
+
[window setFrameOrigin:origin];
|
|
778
|
+
} else {
|
|
779
|
+
[window center];
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
if (LegendPrepareSidebarSplitViewStartup) {
|
|
784
|
+
LegendPrepareSidebarSplitViewStartup(window, windowStyle[@"startupSplitView"]);
|
|
785
|
+
}
|
|
786
|
+
LegendPrecreatedWindows[identifier] = window;
|
|
787
|
+
if (![options[@"deferOrderFront"] boolValue]) {
|
|
788
|
+
[window makeKeyAndOrderFront:nil];
|
|
789
|
+
if (levelNumber) {
|
|
790
|
+
[window orderFrontRegardless];
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
#endif
|
|
796
|
+
|
|
797
|
+
@interface RNWindowManager ()
|
|
798
|
+
#if TARGET_OS_OSX
|
|
799
|
+
<NSWindowDelegate, NSSearchFieldDelegate, NSToolbarDelegate>
|
|
800
|
+
#endif
|
|
801
|
+
@property (nonatomic, strong) NSMutableDictionary<NSString *, id> *windows;
|
|
802
|
+
@property (nonatomic, strong) NSMutableDictionary<NSString *, RCTUIView *> *rootViews;
|
|
803
|
+
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSString *> *moduleNames;
|
|
804
|
+
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSArray<NSTitlebarAccessoryViewController *> *> *titlebarAccessoryControllers;
|
|
805
|
+
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSView *> *titlebarMaterialViews;
|
|
806
|
+
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSArray<NSDictionary *> *> *toolbarItemConfigs;
|
|
807
|
+
@property (nonatomic, strong) NSMutableDictionary<NSString *, CIFilter *> *windowBlurFilters;
|
|
808
|
+
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSDictionary *> *windowOptions;
|
|
809
|
+
@property (nonatomic, strong) NSMutableSet<NSString *> *closeRequestIdentifiers;
|
|
810
|
+
@property (nonatomic, strong, nullable) id displaySleepActivity;
|
|
811
|
+
@property (nonatomic, assign) BOOL hasListeners;
|
|
812
|
+
@property (nonatomic, assign) BOOL mainWindowObserversInstalled;
|
|
813
|
+
#if TARGET_OS_OSX
|
|
814
|
+
- (void)sendToolbarSearchEventForField:(NSSearchField *)searchField submitted:(BOOL)submitted shiftKey:(BOOL)shiftKey;
|
|
815
|
+
- (void)recordWindowOptions:(NSDictionary *)options identifier:(NSString *)identifier moduleName:(NSString *)moduleName;
|
|
816
|
+
#endif
|
|
817
|
+
@end
|
|
818
|
+
|
|
819
|
+
@implementation RNWindowManager
|
|
820
|
+
|
|
821
|
+
RCT_EXPORT_MODULE(NativeWindowManager)
|
|
822
|
+
|
|
823
|
+
+ (BOOL)requiresMainQueueSetup
|
|
824
|
+
{
|
|
825
|
+
return YES;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
- (instancetype)init
|
|
829
|
+
{
|
|
830
|
+
if (self = [super init]) {
|
|
831
|
+
#if TARGET_OS_OSX
|
|
832
|
+
LegendWindowRegistry *registry = LegendWindowRegistry.sharedRegistry;
|
|
833
|
+
_windows = (id)registry.windows;
|
|
834
|
+
_rootViews = (id)registry.rootViews;
|
|
835
|
+
_moduleNames = registry.moduleNames;
|
|
836
|
+
_titlebarAccessoryControllers = registry.titlebarAccessoryControllers;
|
|
837
|
+
_titlebarMaterialViews = registry.titlebarMaterialViews;
|
|
838
|
+
_toolbarItemConfigs = registry.toolbarItemConfigs;
|
|
839
|
+
_windowBlurFilters = registry.windowBlurFilters;
|
|
840
|
+
_windowOptions = registry.windowOptions;
|
|
841
|
+
_closeRequestIdentifiers = registry.closeRequestIdentifiers;
|
|
842
|
+
for (NSString *identifier in _moduleNames.allKeys) {
|
|
843
|
+
NSWindow *window = _windows[identifier];
|
|
844
|
+
SparkRebindWindowDelegate(window, identifier, self);
|
|
845
|
+
[self bindChromeForWindow:window];
|
|
846
|
+
}
|
|
847
|
+
[self bindChromeForWindow:[RNWindowManager getMainWindow]];
|
|
848
|
+
#else
|
|
849
|
+
_windows = [NSMutableDictionary new];
|
|
850
|
+
_rootViews = [NSMutableDictionary new];
|
|
851
|
+
_moduleNames = [NSMutableDictionary new];
|
|
852
|
+
_titlebarAccessoryControllers = [NSMutableDictionary new];
|
|
853
|
+
_titlebarMaterialViews = [NSMutableDictionary new];
|
|
854
|
+
_toolbarItemConfigs = [NSMutableDictionary new];
|
|
855
|
+
_windowBlurFilters = [NSMutableDictionary new];
|
|
856
|
+
_windowOptions = [NSMutableDictionary new];
|
|
857
|
+
_closeRequestIdentifiers = [NSMutableSet new];
|
|
858
|
+
#endif
|
|
859
|
+
#if TARGET_OS_OSX
|
|
860
|
+
#if __has_include(<RNDesktopApp/SparkDesktop.h>)
|
|
861
|
+
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(sparkDesktopEvent:) name:SparkDesktopEvent object:nil];
|
|
862
|
+
#endif
|
|
863
|
+
[NSNotificationCenter.defaultCenter addObserver:self
|
|
864
|
+
selector:@selector(applicationReopenRequested:)
|
|
865
|
+
name:LegendApplicationReopenRequestedNotification
|
|
866
|
+
object:nil];
|
|
867
|
+
[NSNotificationCenter.defaultCenter addObserver:self
|
|
868
|
+
selector:@selector(mainWindowCloseRequested:)
|
|
869
|
+
name:LegendMainWindowCloseRequestedNotification
|
|
870
|
+
object:nil];
|
|
871
|
+
[NSNotificationCenter.defaultCenter addObserver:self
|
|
872
|
+
selector:@selector(applicationWillTerminate:)
|
|
873
|
+
name:NSApplicationWillTerminateNotification
|
|
874
|
+
object:nil];
|
|
875
|
+
[NSNotificationCenter.defaultCenter addObserver:self
|
|
876
|
+
selector:@selector(windowDidBecomeKey:)
|
|
877
|
+
name:NSWindowDidBecomeKeyNotification
|
|
878
|
+
object:nil];
|
|
879
|
+
[NSNotificationCenter.defaultCenter addObserver:self
|
|
880
|
+
selector:@selector(screensChanged:)
|
|
881
|
+
name:NSApplicationDidChangeScreenParametersNotification
|
|
882
|
+
object:nil];
|
|
883
|
+
#endif
|
|
884
|
+
}
|
|
885
|
+
return self;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
#if TARGET_OS_OSX
|
|
889
|
+
- (void)bindChromeControlView:(NSView *)view
|
|
890
|
+
{
|
|
891
|
+
BOOL managed = objc_getAssociatedObject(view, &LegendToolbarControlMetadataKey) != nil ||
|
|
892
|
+
objc_getAssociatedObject(view, &LegendTitlebarControlMetadataKey) != nil ||
|
|
893
|
+
objc_getAssociatedObject(view, &LegendToolbarSearchItemKey) != nil;
|
|
894
|
+
if (managed && [view isKindOfClass:NSControl.class]) {
|
|
895
|
+
((NSControl *)view).target = self;
|
|
896
|
+
if ([view isKindOfClass:NSSearchField.class]) ((NSSearchField *)view).delegate = self;
|
|
897
|
+
}
|
|
898
|
+
for (NSView *child in view.subviews) [self bindChromeControlView:child];
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
- (void)bindChromeForWindow:(NSWindow *)window
|
|
902
|
+
{
|
|
903
|
+
NSToolbar *toolbar = window.toolbar;
|
|
904
|
+
if (objc_getAssociatedObject(toolbar, &LegendToolbarWindowIdentifierKey)) {
|
|
905
|
+
toolbar.delegate = self;
|
|
906
|
+
for (NSToolbarItem *item in toolbar.items) {
|
|
907
|
+
if (objc_getAssociatedObject(item, &LegendToolbarControlMetadataKey)) item.target = self;
|
|
908
|
+
[self bindChromeControlView:item.view];
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
for (NSTitlebarAccessoryViewController *controller in window.titlebarAccessoryViewControllers) {
|
|
912
|
+
[self bindChromeControlView:controller.view];
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
#endif
|
|
916
|
+
|
|
917
|
+
- (NSArray<NSString *> *)supportedEvents
|
|
918
|
+
{
|
|
919
|
+
return @[
|
|
920
|
+
@"onWindowClosed",
|
|
921
|
+
@"onWindowCloseRequested",
|
|
922
|
+
@"onMainWindowMoved",
|
|
923
|
+
@"onMainWindowResized",
|
|
924
|
+
@"onWindowMoved",
|
|
925
|
+
@"onWindowResized",
|
|
926
|
+
@"onApplicationReopenRequested",
|
|
927
|
+
@"onWindowFocused",
|
|
928
|
+
@"onTitlebarControlPressed",
|
|
929
|
+
@"onToolbarItemSelected",
|
|
930
|
+
@"onToolbarSearch",
|
|
931
|
+
@"onDisplaysChanged",
|
|
932
|
+
];
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
- (void)startObserving
|
|
936
|
+
{
|
|
937
|
+
self.hasListeners = YES;
|
|
938
|
+
#if TARGET_OS_OSX
|
|
939
|
+
[self setupMainWindowObservers];
|
|
940
|
+
#endif
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
- (void)stopObserving
|
|
944
|
+
{
|
|
945
|
+
self.hasListeners = NO;
|
|
946
|
+
// Native observers belong to this module, not a JS subscription. React may
|
|
947
|
+
// briefly remove every listener while effects update or a document closes.
|
|
948
|
+
// Keep lifecycle/restoration tracking alive until dealloc; event emission is
|
|
949
|
+
// already gated by hasListeners.
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
- (dispatch_queue_t)methodQueue
|
|
953
|
+
{
|
|
954
|
+
return dispatch_get_main_queue();
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
958
|
+
(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
959
|
+
{
|
|
960
|
+
return std::make_shared<facebook::react::NativeWindowManagerSpecJSI>(params);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
- (NSDictionary *)parseObjectJSON:(NSString *)json
|
|
964
|
+
{
|
|
965
|
+
NSData *data = [json dataUsingEncoding:NSUTF8StringEncoding];
|
|
966
|
+
if (!data) {
|
|
967
|
+
return @{};
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
id value = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
|
|
971
|
+
return [value isKindOfClass:NSDictionary.class] ? value : @{};
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
- (NSString *)jsonStringFromObject:(id)object
|
|
975
|
+
{
|
|
976
|
+
id value = object ?: [NSNull null];
|
|
977
|
+
NSData *data = [NSJSONSerialization dataWithJSONObject:value options:0 error:nil];
|
|
978
|
+
return data ? [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] : @"null";
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
#if TARGET_OS_OSX
|
|
982
|
+
- (void)removeTitlebarMaterialForIdentifier:(NSString *)identifier
|
|
983
|
+
{
|
|
984
|
+
NSView *materialView = self.titlebarMaterialViews[identifier];
|
|
985
|
+
[materialView removeFromSuperview];
|
|
986
|
+
[self.titlebarMaterialViews removeObjectForKey:identifier];
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
- (NSView *)createTitlebarMaterialView:(NSString *)material frame:(NSRect)frame
|
|
990
|
+
{
|
|
991
|
+
if ([material isEqualToString:@"glass"]) {
|
|
992
|
+
if (@available(macOS 26.0, *)) {
|
|
993
|
+
return LegendCreateOverscannedGlassEffectView(frame);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
NSVisualEffectView *effectView = [[NSVisualEffectView alloc] initWithFrame:frame];
|
|
998
|
+
effectView.material = LegendVisualEffectMaterialForTitlebarMaterial(material);
|
|
999
|
+
effectView.blendingMode = NSVisualEffectBlendingModeBehindWindow;
|
|
1000
|
+
effectView.state = NSVisualEffectStateFollowsWindowActiveState;
|
|
1001
|
+
effectView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
|
1002
|
+
effectView.wantsLayer = YES;
|
|
1003
|
+
effectView.layer.backgroundColor = NSColor.clearColor.CGColor;
|
|
1004
|
+
return effectView;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
- (void)applyTitlebarMaterialFromOptions:(NSDictionary *)options toWindow:(NSWindow *)window identifier:(NSString *)identifier
|
|
1008
|
+
{
|
|
1009
|
+
NSDictionary *windowStyle = [options[@"windowStyle"] isKindOfClass:NSDictionary.class] ? options[@"windowStyle"] : @{};
|
|
1010
|
+
if (!LegendDictionaryHasKey(windowStyle, @"titlebarMaterial")) {
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
NSString *material = [windowStyle[@"titlebarMaterial"] isKindOfClass:NSString.class] ? windowStyle[@"titlebarMaterial"] : nil;
|
|
1015
|
+
[self removeTitlebarMaterialForIdentifier:identifier];
|
|
1016
|
+
|
|
1017
|
+
if (material.length == 0 || [material isEqualToString:@"none"]) {
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
NSView *frameView = window.contentView.superview;
|
|
1022
|
+
if (!frameView) {
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
NSView *materialView = [self createTitlebarMaterialView:material frame:LegendTitlebarMaterialFrame(window, frameView)];
|
|
1027
|
+
if ([materialView isKindOfClass:NSVisualEffectView.class]) {
|
|
1028
|
+
NSVisualEffectView *effectView = (NSVisualEffectView *)materialView;
|
|
1029
|
+
NSString *blendingMode = [windowStyle[@"titlebarMaterialBlendingMode"] isKindOfClass:NSString.class]
|
|
1030
|
+
? windowStyle[@"titlebarMaterialBlendingMode"]
|
|
1031
|
+
: nil;
|
|
1032
|
+
NSString *state = [windowStyle[@"titlebarMaterialState"] isKindOfClass:NSString.class]
|
|
1033
|
+
? windowStyle[@"titlebarMaterialState"]
|
|
1034
|
+
: nil;
|
|
1035
|
+
effectView.blendingMode = LegendVisualEffectBlendingModeForName(blendingMode);
|
|
1036
|
+
effectView.state = LegendVisualEffectStateForName(state);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
materialView.translatesAutoresizingMaskIntoConstraints = YES;
|
|
1040
|
+
materialView.autoresizingMask = NSViewWidthSizable | NSViewMinYMargin;
|
|
1041
|
+
[frameView addSubview:materialView positioned:NSWindowAbove relativeTo:window.contentView];
|
|
1042
|
+
self.titlebarMaterialViews[identifier] = materialView;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
- (void)removeTitlebarControlsForIdentifier:(NSString *)identifier fromWindow:(NSWindow *)window
|
|
1046
|
+
{
|
|
1047
|
+
NSArray<NSTitlebarAccessoryViewController *> *controllers = self.titlebarAccessoryControllers[identifier] ?: @[];
|
|
1048
|
+
for (NSTitlebarAccessoryViewController *controller in controllers) {
|
|
1049
|
+
NSUInteger index = [window.titlebarAccessoryViewControllers indexOfObjectIdenticalTo:controller];
|
|
1050
|
+
if (index != NSNotFound) {
|
|
1051
|
+
[window removeTitlebarAccessoryViewControllerAtIndex:index];
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
[self.titlebarAccessoryControllers removeObjectForKey:identifier];
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
- (void)applyTitlebarControlsFromOptions:(NSDictionary *)options toWindow:(NSWindow *)window identifier:(NSString *)identifier
|
|
1058
|
+
{
|
|
1059
|
+
NSDictionary *windowStyle = [options[@"windowStyle"] isKindOfClass:NSDictionary.class] ? options[@"windowStyle"] : @{};
|
|
1060
|
+
if (!LegendDictionaryHasKey(windowStyle, @"titlebarControls")) {
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
[self removeTitlebarControlsForIdentifier:identifier fromWindow:window];
|
|
1065
|
+
|
|
1066
|
+
NSArray *titlebarControls = [windowStyle[@"titlebarControls"] isKindOfClass:NSArray.class]
|
|
1067
|
+
? windowStyle[@"titlebarControls"]
|
|
1068
|
+
: @[];
|
|
1069
|
+
NSMutableArray<NSTitlebarAccessoryViewController *> *controllers = [NSMutableArray new];
|
|
1070
|
+
|
|
1071
|
+
for (id controlCandidate in titlebarControls) {
|
|
1072
|
+
if (![controlCandidate isKindOfClass:NSDictionary.class]) {
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
NSDictionary *control = (NSDictionary *)controlCandidate;
|
|
1077
|
+
NSString *type = [control[@"type"] isKindOfClass:NSString.class] ? control[@"type"] : nil;
|
|
1078
|
+
NSString *controlId = [control[@"id"] isKindOfClass:NSString.class] ? control[@"id"] : nil;
|
|
1079
|
+
if (![type isEqualToString:@"button"] || controlId.length == 0) {
|
|
1080
|
+
continue;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
NSString *label = [control[@"label"] isKindOfClass:NSString.class] ? control[@"label"] : controlId;
|
|
1084
|
+
NSString *systemImageName = [control[@"systemImageName"] isKindOfClass:NSString.class] ? control[@"systemImageName"] : nil;
|
|
1085
|
+
NSImage *image = nil;
|
|
1086
|
+
if (systemImageName.length > 0) {
|
|
1087
|
+
if (@available(macOS 11.0, *)) {
|
|
1088
|
+
image = [NSImage imageWithSystemSymbolName:systemImageName accessibilityDescription:label];
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
NSSegmentedControl *segmentedControl = [[NSSegmentedControl alloc] initWithFrame:NSZeroRect];
|
|
1093
|
+
segmentedControl.segmentCount = 1;
|
|
1094
|
+
segmentedControl.segmentStyle = NSSegmentStyleSeparated;
|
|
1095
|
+
segmentedControl.trackingMode = NSSegmentSwitchTrackingSelectAny;
|
|
1096
|
+
[segmentedControl setSelected:[control[@"selected"] boolValue] forSegment:0];
|
|
1097
|
+
segmentedControl.target = self;
|
|
1098
|
+
segmentedControl.action = @selector(titlebarControlPressed:);
|
|
1099
|
+
segmentedControl.controlSize = NSControlSizeRegular;
|
|
1100
|
+
[segmentedControl setEnabled:LegendDictionaryHasKey(control, @"enabled") ? [control[@"enabled"] boolValue] : YES forSegment:0];
|
|
1101
|
+
if (image) {
|
|
1102
|
+
[segmentedControl setImage:image forSegment:0];
|
|
1103
|
+
[segmentedControl setImageScaling:NSImageScaleProportionallyDown forSegment:0];
|
|
1104
|
+
} else {
|
|
1105
|
+
[segmentedControl setLabel:label forSegment:0];
|
|
1106
|
+
}
|
|
1107
|
+
if (@available(macOS 10.13, *)) {
|
|
1108
|
+
[segmentedControl setToolTip:[control[@"tooltip"] isKindOfClass:NSString.class] ? control[@"tooltip"] : label forSegment:0];
|
|
1109
|
+
}
|
|
1110
|
+
CGFloat segmentWidth = image ? 36 : MAX(84, [label sizeWithAttributes:@{NSFontAttributeName: segmentedControl.font ?: [NSFont systemFontOfSize:NSFont.systemFontSize]}].width + 24);
|
|
1111
|
+
[segmentedControl setWidth:segmentWidth forSegment:0];
|
|
1112
|
+
segmentedControl.frame = NSMakeRect(0, 0, segmentWidth, MAX(28, segmentedControl.fittingSize.height));
|
|
1113
|
+
objc_setAssociatedObject(segmentedControl, &LegendTitlebarControlMetadataKey, @{
|
|
1114
|
+
@"controlId": controlId,
|
|
1115
|
+
@"windowIdentifier": identifier ?: @"",
|
|
1116
|
+
@"instanceId": objc_getAssociatedObject(window, NSSelectorFromString(@"sparkWindowInstanceId")) ?: @"",
|
|
1117
|
+
@"selected": control[@"selected"] ?: @NO,
|
|
1118
|
+
}, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
1119
|
+
|
|
1120
|
+
NSTitlebarAccessoryViewController *controller = [NSTitlebarAccessoryViewController new];
|
|
1121
|
+
controller.view = segmentedControl;
|
|
1122
|
+
NSString *placement = [control[@"placement"] isKindOfClass:NSString.class] ? control[@"placement"] : @"left";
|
|
1123
|
+
controller.layoutAttribute = [placement isEqualToString:@"right"] ? NSLayoutAttributeRight : NSLayoutAttributeLeft;
|
|
1124
|
+
[window addTitlebarAccessoryViewController:controller];
|
|
1125
|
+
[controllers addObject:controller];
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
self.titlebarAccessoryControllers[identifier] = controllers;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
- (void)titlebarControlPressed:(id)sender
|
|
1132
|
+
{
|
|
1133
|
+
id metadata = objc_getAssociatedObject(sender, &LegendTitlebarControlMetadataKey);
|
|
1134
|
+
NSDictionary *representedObject = [metadata isKindOfClass:NSDictionary.class]
|
|
1135
|
+
? metadata
|
|
1136
|
+
: @{};
|
|
1137
|
+
NSString *identifier = [representedObject[@"windowIdentifier"] isKindOfClass:NSString.class]
|
|
1138
|
+
? representedObject[@"windowIdentifier"]
|
|
1139
|
+
: @"";
|
|
1140
|
+
NSString *controlId = [representedObject[@"controlId"] isKindOfClass:NSString.class]
|
|
1141
|
+
? representedObject[@"controlId"]
|
|
1142
|
+
: @"";
|
|
1143
|
+
|
|
1144
|
+
if ([sender isKindOfClass:NSSegmentedControl.class]) [(NSSegmentedControl *)sender setSelected:[representedObject[@"selected"] boolValue] forSegment:0];
|
|
1145
|
+
[self sendWindowEventWithName:@"onTitlebarControlPressed"
|
|
1146
|
+
body:@{@"identifier": identifier, @"controlId": controlId, @"instanceId": representedObject[@"instanceId"] ?: @""}];
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
- (NSString *)toolbarItemIdentifierForConfig:(NSDictionary *)config
|
|
1150
|
+
{
|
|
1151
|
+
NSString *itemId = [config[@"id"] isKindOfClass:NSString.class] ? config[@"id"] : nil;
|
|
1152
|
+
return itemId.length > 0 ? [@"legend.toolbar." stringByAppendingString:itemId] : nil;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
- (NSDictionary *)toolbarItemConfigForIdentifier:(NSToolbarItemIdentifier)itemIdentifier toolbar:(NSToolbar *)toolbar
|
|
1156
|
+
{
|
|
1157
|
+
if (![itemIdentifier hasPrefix:@"legend.toolbar."]) {
|
|
1158
|
+
return nil;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
NSArray<NSDictionary *> *configs = self.toolbarItemConfigs[toolbar.identifier] ?: @[];
|
|
1162
|
+
for (NSDictionary *config in configs) {
|
|
1163
|
+
NSString *configIdentifier = [self toolbarItemIdentifierForConfig:config];
|
|
1164
|
+
if ([configIdentifier isEqualToString:itemIdentifier]) {
|
|
1165
|
+
return config;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
return nil;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
- (NSArray<NSToolbarItemIdentifier> *)toolbarItemIdentifiersForToolbar:(NSToolbar *)toolbar
|
|
1172
|
+
{
|
|
1173
|
+
NSMutableArray<NSToolbarItemIdentifier> *leadingIdentifiers = [NSMutableArray new];
|
|
1174
|
+
NSMutableArray<NSToolbarItemIdentifier> *trailingIdentifiers = [NSMutableArray new];
|
|
1175
|
+
NSArray<NSDictionary *> *configs = self.toolbarItemConfigs[toolbar.identifier] ?: @[];
|
|
1176
|
+
for (NSDictionary *config in configs) {
|
|
1177
|
+
NSString *itemIdentifier = [self toolbarItemIdentifierForConfig:config];
|
|
1178
|
+
if (itemIdentifier.length > 0) {
|
|
1179
|
+
NSString *placement = [config[@"placement"] isKindOfClass:NSString.class] ? config[@"placement"] : @"trailing";
|
|
1180
|
+
if ([placement isEqualToString:@"leading"]) {
|
|
1181
|
+
[leadingIdentifiers addObject:itemIdentifier];
|
|
1182
|
+
} else {
|
|
1183
|
+
[trailingIdentifiers addObject:itemIdentifier];
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
NSMutableArray<NSToolbarItemIdentifier> *identifiers = [NSMutableArray new];
|
|
1188
|
+
[identifiers addObjectsFromArray:leadingIdentifiers];
|
|
1189
|
+
[identifiers addObject:NSToolbarFlexibleSpaceItemIdentifier];
|
|
1190
|
+
[identifiers addObjectsFromArray:trailingIdentifiers];
|
|
1191
|
+
return identifiers;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
- (NSToolbar *)ensureToolbarForWindow:(NSWindow *)window identifier:(NSString *)identifier
|
|
1195
|
+
{
|
|
1196
|
+
NSToolbar *toolbar = window.toolbar;
|
|
1197
|
+
if (!toolbar || !objc_getAssociatedObject(toolbar, &LegendToolbarWindowIdentifierKey)) {
|
|
1198
|
+
toolbar = LegendCreateToolbar(identifier);
|
|
1199
|
+
window.toolbar = toolbar;
|
|
1200
|
+
}
|
|
1201
|
+
toolbar.delegate = self;
|
|
1202
|
+
toolbar.allowsUserCustomization = NO;
|
|
1203
|
+
toolbar.autosavesConfiguration = NO;
|
|
1204
|
+
return toolbar;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
- (void)applyToolbarItemsFromOptions:(NSDictionary *)options toWindow:(NSWindow *)window identifier:(NSString *)identifier
|
|
1208
|
+
{
|
|
1209
|
+
NSDictionary *windowStyle = [options[@"windowStyle"] isKindOfClass:NSDictionary.class] ? options[@"windowStyle"] : @{};
|
|
1210
|
+
if (!LegendDictionaryHasKey(windowStyle, @"toolbarItems")) {
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
NSArray *toolbarItems = [windowStyle[@"toolbarItems"] isKindOfClass:NSArray.class] ? windowStyle[@"toolbarItems"] : @[];
|
|
1215
|
+
NSToolbar *toolbar = [self ensureToolbarForWindow:window identifier:identifier];
|
|
1216
|
+
NSMutableArray<NSDictionary *> *configs = [NSMutableArray new];
|
|
1217
|
+
for (id item in toolbarItems) {
|
|
1218
|
+
if ([item isKindOfClass:NSDictionary.class]) {
|
|
1219
|
+
NSMutableDictionary *config = [item mutableCopy]; config[@"instanceId"] = objc_getAssociatedObject(window, NSSelectorFromString(@"sparkWindowInstanceId")) ?: @""; [configs addObject:config];
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
NSArray<NSDictionary *> *previousConfigs = self.toolbarItemConfigs[toolbar.identifier];
|
|
1223
|
+
BOOL menuUpdate = previousConfigs != nil && previousConfigs.count == configs.count;
|
|
1224
|
+
for (NSUInteger index = 0; menuUpdate && index < configs.count; index++) {
|
|
1225
|
+
NSMutableDictionary *previous = [previousConfigs[index] mutableCopy];
|
|
1226
|
+
NSMutableDictionary *next = [configs[index] mutableCopy];
|
|
1227
|
+
if ([next[@"type"] isEqual:@"menuButton"]) {
|
|
1228
|
+
[previous removeObjectForKey:@"menuItems"];
|
|
1229
|
+
[next removeObjectForKey:@"menuItems"];
|
|
1230
|
+
}
|
|
1231
|
+
menuUpdate = [previous isEqualToDictionary:next];
|
|
1232
|
+
}
|
|
1233
|
+
self.toolbarItemConfigs[toolbar.identifier] = configs;
|
|
1234
|
+
if (menuUpdate) {
|
|
1235
|
+
for (NSDictionary *config in configs) {
|
|
1236
|
+
if (![config[@"type"] isEqual:@"menuButton"]) continue;
|
|
1237
|
+
NSString *itemId = config[@"id"];
|
|
1238
|
+
if (![itemId isKindOfClass:NSString.class] || itemId.length == 0) { menuUpdate = NO; break; }
|
|
1239
|
+
NSString *itemIdentifier = [@"legend.toolbar." stringByAppendingString:itemId];
|
|
1240
|
+
NSToolbarItem *item = nil;
|
|
1241
|
+
for (NSToolbarItem *candidate in toolbar.items) {
|
|
1242
|
+
if ([candidate.itemIdentifier isEqual:itemIdentifier]) { item = candidate; break; }
|
|
1243
|
+
}
|
|
1244
|
+
id control = item.view ?: item;
|
|
1245
|
+
NSDictionary *metadata = control ? objc_getAssociatedObject(control, &LegendToolbarControlMetadataKey) : nil;
|
|
1246
|
+
if (!metadata) { menuUpdate = NO; break; }
|
|
1247
|
+
NSMutableDictionary *nextMetadata = [metadata mutableCopy];
|
|
1248
|
+
nextMetadata[@"menuItems"] = config[@"menuItems"] ?: @[];
|
|
1249
|
+
objc_setAssociatedObject(control, &LegendToolbarControlMetadataKey, nextMetadata, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
1250
|
+
}
|
|
1251
|
+
if (menuUpdate) return;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
while (toolbar.items.count > 0) {
|
|
1255
|
+
[toolbar removeItemAtIndex:toolbar.items.count - 1];
|
|
1256
|
+
}
|
|
1257
|
+
for (NSToolbarItemIdentifier itemIdentifier in [self toolbarDefaultItemIdentifiers:toolbar]) {
|
|
1258
|
+
[toolbar insertItemWithItemIdentifier:itemIdentifier atIndex:toolbar.items.count];
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
- (NSArray<NSToolbarItemIdentifier> *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar
|
|
1263
|
+
{
|
|
1264
|
+
NSMutableArray<NSToolbarItemIdentifier> *identifiers = [[self toolbarItemIdentifiersForToolbar:toolbar] mutableCopy];
|
|
1265
|
+
[identifiers addObject:NSToolbarSpaceItemIdentifier];
|
|
1266
|
+
return identifiers;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
- (NSArray<NSToolbarItemIdentifier> *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar
|
|
1270
|
+
{
|
|
1271
|
+
return [self toolbarItemIdentifiersForToolbar:toolbar];
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
- (NSArray<NSToolbarItemIdentifier> *)toolbarSelectableItemIdentifiers:(NSToolbar *)toolbar
|
|
1275
|
+
{
|
|
1276
|
+
return @[];
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
- (NSImage *)toolbarSearchImageWithLabel:(NSString *)label
|
|
1280
|
+
{
|
|
1281
|
+
if (@available(macOS 11.0, *)) {
|
|
1282
|
+
return [NSImage imageWithSystemSymbolName:@"magnifyingglass" accessibilityDescription:label ?: @"Search"];
|
|
1283
|
+
}
|
|
1284
|
+
return [NSImage imageNamed:NSImageNameTouchBarSearchTemplate];
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
- (void)focusToolbarSearchField:(NSSearchField *)searchField
|
|
1288
|
+
{
|
|
1289
|
+
id metadata = objc_getAssociatedObject(searchField, &LegendToolbarControlMetadataKey);
|
|
1290
|
+
NSDictionary *representedObject = [metadata isKindOfClass:NSDictionary.class]
|
|
1291
|
+
? metadata
|
|
1292
|
+
: @{};
|
|
1293
|
+
NSString *identifier = [representedObject[@"windowIdentifier"] isKindOfClass:NSString.class]
|
|
1294
|
+
? representedObject[@"windowIdentifier"]
|
|
1295
|
+
: @"";
|
|
1296
|
+
NSWindow *window = (NSWindow *)self.windows[identifier];
|
|
1297
|
+
[window makeKeyAndOrderFront:nil];
|
|
1298
|
+
[window makeFirstResponder:searchField];
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
- (void)setToolbarSearchItem:(NSToolbarItem *)toolbarItem
|
|
1302
|
+
expanded:(BOOL)expanded
|
|
1303
|
+
value:(NSString *)value
|
|
1304
|
+
focus:(BOOL)focus
|
|
1305
|
+
sendChange:(BOOL)sendChange
|
|
1306
|
+
animated:(BOOL)animated
|
|
1307
|
+
{
|
|
1308
|
+
if (!toolbarItem) {
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
id associatedField = objc_getAssociatedObject(toolbarItem, &LegendToolbarSearchFieldKey);
|
|
1313
|
+
id associatedButton = objc_getAssociatedObject(toolbarItem, &LegendToolbarSearchButtonKey);
|
|
1314
|
+
id associatedContainer = objc_getAssociatedObject(toolbarItem, &LegendToolbarSearchContainerKey);
|
|
1315
|
+
id associatedWidthConstraint = objc_getAssociatedObject(toolbarItem, &LegendToolbarSearchWidthConstraintKey);
|
|
1316
|
+
if (![associatedField isKindOfClass:NSSearchField.class]) {
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
NSSearchField *searchField = (NSSearchField *)associatedField;
|
|
1321
|
+
NSButton *button = [associatedButton isKindOfClass:NSButton.class] ? (NSButton *)associatedButton : nil;
|
|
1322
|
+
NSView *container = [associatedContainer isKindOfClass:NSView.class] ? (NSView *)associatedContainer : toolbarItem.view;
|
|
1323
|
+
NSLayoutConstraint *widthConstraint = [associatedWidthConstraint isKindOfClass:NSLayoutConstraint.class]
|
|
1324
|
+
? (NSLayoutConstraint *)associatedWidthConstraint
|
|
1325
|
+
: nil;
|
|
1326
|
+
NSNumber *widthNumber = objc_getAssociatedObject(toolbarItem, &LegendToolbarSearchWidthKey);
|
|
1327
|
+
CGFloat width = [widthNumber isKindOfClass:NSNumber.class] ? widthNumber.doubleValue : 240;
|
|
1328
|
+
CGFloat targetWidth = expanded ? width : LegendToolbarSearchCollapsedSize;
|
|
1329
|
+
|
|
1330
|
+
if (value) {
|
|
1331
|
+
searchField.stringValue = value;
|
|
1332
|
+
}
|
|
1333
|
+
if (expanded) {
|
|
1334
|
+
searchField.hidden = NO;
|
|
1335
|
+
button.hidden = NO;
|
|
1336
|
+
}
|
|
1337
|
+
if (focus) {
|
|
1338
|
+
[self focusToolbarSearchField:searchField];
|
|
1339
|
+
}
|
|
1340
|
+
if (container) {
|
|
1341
|
+
NSRect frame = container.frame;
|
|
1342
|
+
frame.size.width = targetWidth;
|
|
1343
|
+
if (animated) {
|
|
1344
|
+
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
|
|
1345
|
+
context.duration = 0.18;
|
|
1346
|
+
context.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
|
|
1347
|
+
widthConstraint.animator.constant = targetWidth;
|
|
1348
|
+
container.animator.frame = frame;
|
|
1349
|
+
searchField.animator.alphaValue = expanded ? 1 : 0;
|
|
1350
|
+
button.animator.alphaValue = expanded ? 0 : 1;
|
|
1351
|
+
[container.superview.animator layoutSubtreeIfNeeded];
|
|
1352
|
+
[container.animator layoutSubtreeIfNeeded];
|
|
1353
|
+
} completionHandler:^{
|
|
1354
|
+
button.hidden = expanded;
|
|
1355
|
+
searchField.hidden = !expanded;
|
|
1356
|
+
}];
|
|
1357
|
+
} else {
|
|
1358
|
+
widthConstraint.constant = targetWidth;
|
|
1359
|
+
container.frame = frame;
|
|
1360
|
+
searchField.alphaValue = expanded ? 1 : 0;
|
|
1361
|
+
button.alphaValue = expanded ? 0 : 1;
|
|
1362
|
+
button.hidden = expanded;
|
|
1363
|
+
searchField.hidden = !expanded;
|
|
1364
|
+
[container layoutSubtreeIfNeeded];
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
if (sendChange) {
|
|
1368
|
+
[self sendToolbarSearchEventForField:searchField submitted:NO shiftKey:NO];
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
- (void)collapseToolbarSearchItem:(NSToolbarItem *)toolbarItem animated:(BOOL)animated
|
|
1373
|
+
{
|
|
1374
|
+
[self setToolbarSearchItem:toolbarItem
|
|
1375
|
+
expanded:NO
|
|
1376
|
+
value:nil
|
|
1377
|
+
focus:NO
|
|
1378
|
+
sendChange:NO
|
|
1379
|
+
animated:animated];
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
- (void)expandToolbarSearchItem:(NSToolbarItem *)toolbarItem
|
|
1383
|
+
value:(NSString *)value
|
|
1384
|
+
focus:(BOOL)focus
|
|
1385
|
+
sendChange:(BOOL)sendChange
|
|
1386
|
+
animated:(BOOL)animated
|
|
1387
|
+
{
|
|
1388
|
+
[self setToolbarSearchItem:toolbarItem
|
|
1389
|
+
expanded:YES
|
|
1390
|
+
value:value
|
|
1391
|
+
focus:focus
|
|
1392
|
+
sendChange:sendChange
|
|
1393
|
+
animated:animated];
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar
|
|
1397
|
+
itemForItemIdentifier:(NSToolbarItemIdentifier)itemIdentifier
|
|
1398
|
+
willBeInsertedIntoToolbar:(BOOL)flag
|
|
1399
|
+
{
|
|
1400
|
+
NSDictionary *config = [self toolbarItemConfigForIdentifier:itemIdentifier toolbar:toolbar];
|
|
1401
|
+
if (!config) {
|
|
1402
|
+
return nil;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
NSString *type = [config[@"type"] isKindOfClass:NSString.class] ? config[@"type"] : nil;
|
|
1406
|
+
if ([type isEqualToString:@"button"] || [type isEqualToString:@"menuButton"]) {
|
|
1407
|
+
NSString *itemId = [config[@"id"] isKindOfClass:NSString.class] ? config[@"id"] : @"";
|
|
1408
|
+
NSString *label = [config[@"label"] isKindOfClass:NSString.class] ? config[@"label"] : itemId;
|
|
1409
|
+
NSString *tooltip = [config[@"tooltip"] isKindOfClass:NSString.class] ? config[@"tooltip"] : label;
|
|
1410
|
+
NSString *systemImageName = [config[@"systemImageName"] isKindOfClass:NSString.class] ? config[@"systemImageName"] : nil;
|
|
1411
|
+
NSImage *image = nil;
|
|
1412
|
+
if (systemImageName.length > 0) {
|
|
1413
|
+
if (@available(macOS 11.0, *)) {
|
|
1414
|
+
image = [NSImage imageWithSystemSymbolName:systemImageName accessibilityDescription:label];
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
NSArray *menuItems = [config[@"menuItems"] isKindOfClass:NSArray.class] ? config[@"menuItems"] : @[];
|
|
1418
|
+
NSNumber *widthNumber = [config[@"width"] isKindOfClass:NSNumber.class] ? config[@"width"] : nil;
|
|
1419
|
+
BOOL isMenuButton = [type isEqualToString:@"menuButton"] || menuItems.count > 0;
|
|
1420
|
+
|
|
1421
|
+
NSToolbarItem *toolbarItem = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
|
|
1422
|
+
toolbarItem.label = label;
|
|
1423
|
+
toolbarItem.paletteLabel = label;
|
|
1424
|
+
toolbarItem.toolTip = tooltip;
|
|
1425
|
+
toolbarItem.enabled = LegendDictionaryHasKey(config, @"enabled") ? [config[@"enabled"] boolValue] : YES;
|
|
1426
|
+
NSString *placement = [config[@"placement"] isKindOfClass:NSString.class] ? config[@"placement"] : @"trailing";
|
|
1427
|
+
if (@available(macOS 10.15, *)) {
|
|
1428
|
+
toolbarItem.bordered = LegendDictionaryHasKey(config, @"bordered") ? [config[@"bordered"] boolValue] : YES;
|
|
1429
|
+
}
|
|
1430
|
+
if (@available(macOS 11.0, *)) {
|
|
1431
|
+
toolbarItem.navigational = [placement isEqualToString:@"leading"];
|
|
1432
|
+
}
|
|
1433
|
+
NSDictionary *metadata = @{
|
|
1434
|
+
@"itemId": itemId,
|
|
1435
|
+
@"menuItems": menuItems,
|
|
1436
|
+
@"value": [config[@"value"] isKindOfClass:NSString.class] ? config[@"value"] : @"",
|
|
1437
|
+
@"windowIdentifier": LegendToolbarWindowIdentifier(toolbar),
|
|
1438
|
+
@"instanceId": config[@"instanceId"] ?: @"",
|
|
1439
|
+
};
|
|
1440
|
+
if (isMenuButton) {
|
|
1441
|
+
// The fallback name identifies the item; it is never what the control displays, so an
|
|
1442
|
+
// item without a label stays a bare icon instead of printing that identifier.
|
|
1443
|
+
NSString *title = [config[@"label"] isKindOfClass:NSString.class] ? config[@"label"] : @"";
|
|
1444
|
+
NSButton *button = [NSButton buttonWithTitle:title target:self action:@selector(toolbarButtonItemPressed:)];
|
|
1445
|
+
button.bezelStyle = NSBezelStyleRounded;
|
|
1446
|
+
button.controlSize = NSControlSizeRegular;
|
|
1447
|
+
button.enabled = toolbarItem.enabled;
|
|
1448
|
+
button.toolTip = tooltip;
|
|
1449
|
+
if (LegendDictionaryHasKey(config, @"monospacedDigits") && [config[@"monospacedDigits"] boolValue]) {
|
|
1450
|
+
button.font = [NSFont monospacedDigitSystemFontOfSize:NSFont.systemFontSize weight:NSFontWeightSemibold];
|
|
1451
|
+
}
|
|
1452
|
+
if (image) {
|
|
1453
|
+
button.image = image;
|
|
1454
|
+
button.imagePosition = title.length > 0 ? NSImageLeft : NSImageOnly;
|
|
1455
|
+
}
|
|
1456
|
+
NSFont *font = button.font ?: [NSFont systemFontOfSize:NSFont.systemFontSize];
|
|
1457
|
+
CGFloat textWidth = [title sizeWithAttributes:@{NSFontAttributeName: font}].width + (title.length > 0 ? (image ? 64 : 52) : 0);
|
|
1458
|
+
CGFloat buttonWidth = widthNumber ? widthNumber.doubleValue
|
|
1459
|
+
: MAX(title.length > 0 ? 148 : LegendToolbarIconControlWidth, textWidth);
|
|
1460
|
+
button.frame = NSMakeRect(0, 0, buttonWidth, MAX(28, button.fittingSize.height));
|
|
1461
|
+
objc_setAssociatedObject(button, &LegendToolbarControlMetadataKey, metadata, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
1462
|
+
toolbarItem.view = button;
|
|
1463
|
+
} else {
|
|
1464
|
+
toolbarItem.image = image;
|
|
1465
|
+
toolbarItem.target = self;
|
|
1466
|
+
toolbarItem.action = @selector(toolbarButtonItemPressed:);
|
|
1467
|
+
objc_setAssociatedObject(toolbarItem, &LegendToolbarControlMetadataKey, metadata, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
1468
|
+
}
|
|
1469
|
+
return toolbarItem;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
if ([type isEqualToString:@"label"]) {
|
|
1473
|
+
NSString *itemId = [config[@"id"] isKindOfClass:NSString.class] ? config[@"id"] : @"";
|
|
1474
|
+
NSString *label = [config[@"label"] isKindOfClass:NSString.class] ? config[@"label"] : itemId;
|
|
1475
|
+
NSString *text = [config[@"text"] isKindOfClass:NSString.class] ? config[@"text"] : @"";
|
|
1476
|
+
NSNumber *widthNumber = [config[@"width"] isKindOfClass:NSNumber.class] ? config[@"width"] : nil;
|
|
1477
|
+
CGFloat width = widthNumber ? widthNumber.doubleValue : MAX(44, [text sizeWithAttributes:nil].width + 12);
|
|
1478
|
+
|
|
1479
|
+
NSTextField *textField = [NSTextField labelWithString:text];
|
|
1480
|
+
textField.translatesAutoresizingMaskIntoConstraints = NO;
|
|
1481
|
+
textField.alignment = NSTextAlignmentRight;
|
|
1482
|
+
textField.font = [NSFont monospacedDigitSystemFontOfSize:NSFont.systemFontSize weight:NSFontWeightSemibold];
|
|
1483
|
+
textField.textColor = NSColor.labelColor;
|
|
1484
|
+
textField.toolTip = label;
|
|
1485
|
+
[textField.widthAnchor constraintEqualToConstant:width].active = YES;
|
|
1486
|
+
|
|
1487
|
+
NSToolbarItem *toolbarItem = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
|
|
1488
|
+
toolbarItem.view = textField;
|
|
1489
|
+
toolbarItem.label = label;
|
|
1490
|
+
toolbarItem.paletteLabel = label;
|
|
1491
|
+
toolbarItem.toolTip = label;
|
|
1492
|
+
return toolbarItem;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
if ([type isEqualToString:@"search"]) {
|
|
1496
|
+
NSString *itemId = [config[@"id"] isKindOfClass:NSString.class] ? config[@"id"] : @"";
|
|
1497
|
+
NSString *label = [config[@"label"] isKindOfClass:NSString.class] ? config[@"label"] : itemId;
|
|
1498
|
+
NSString *placeholder = [config[@"placeholder"] isKindOfClass:NSString.class] ? config[@"placeholder"] : @"Search";
|
|
1499
|
+
NSString *value = [config[@"value"] isKindOfClass:NSString.class] ? config[@"value"] : @"";
|
|
1500
|
+
NSNumber *widthNumber = [config[@"width"] isKindOfClass:NSNumber.class] ? config[@"width"] : nil;
|
|
1501
|
+
CGFloat width = widthNumber ? widthNumber.doubleValue : 240;
|
|
1502
|
+
BOOL collapses = LegendDictionaryHasKey(config, @"collapses") && [config[@"collapses"] boolValue];
|
|
1503
|
+
NSDictionary *metadata = @{
|
|
1504
|
+
@"itemId": itemId,
|
|
1505
|
+
@"windowIdentifier": LegendToolbarWindowIdentifier(toolbar),
|
|
1506
|
+
@"instanceId": config[@"instanceId"] ?: @"",
|
|
1507
|
+
};
|
|
1508
|
+
|
|
1509
|
+
NSSearchField *searchField = nil;
|
|
1510
|
+
NSToolbarItem *toolbarItem = nil;
|
|
1511
|
+
if (collapses) {
|
|
1512
|
+
CGFloat initialWidth = value.length > 0 ? width : LegendToolbarSearchCollapsedSize;
|
|
1513
|
+
NSView *container = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, initialWidth, LegendToolbarSearchCollapsedSize)];
|
|
1514
|
+
container.translatesAutoresizingMaskIntoConstraints = NO;
|
|
1515
|
+
NSLayoutConstraint *containerWidthConstraint = [container.widthAnchor constraintEqualToConstant:initialWidth];
|
|
1516
|
+
containerWidthConstraint.active = YES;
|
|
1517
|
+
[container.heightAnchor constraintEqualToConstant:LegendToolbarSearchCollapsedSize].active = YES;
|
|
1518
|
+
|
|
1519
|
+
NSButton *searchButton = [NSButton buttonWithImage:[self toolbarSearchImageWithLabel:label]
|
|
1520
|
+
target:self
|
|
1521
|
+
action:@selector(toolbarSearchButtonPressed:)];
|
|
1522
|
+
searchButton.translatesAutoresizingMaskIntoConstraints = NO;
|
|
1523
|
+
searchButton.bordered = YES;
|
|
1524
|
+
searchButton.bezelStyle = NSBezelStyleCircular;
|
|
1525
|
+
searchButton.controlSize = NSControlSizeRegular;
|
|
1526
|
+
searchButton.imagePosition = NSImageOnly;
|
|
1527
|
+
searchButton.toolTip = placeholder;
|
|
1528
|
+
searchButton.enabled = LegendDictionaryHasKey(config, @"enabled") ? [config[@"enabled"] boolValue] : YES;
|
|
1529
|
+
[container addSubview:searchButton];
|
|
1530
|
+
[NSLayoutConstraint activateConstraints:@[
|
|
1531
|
+
[searchButton.centerXAnchor constraintEqualToAnchor:container.centerXAnchor],
|
|
1532
|
+
[searchButton.centerYAnchor constraintEqualToAnchor:container.centerYAnchor],
|
|
1533
|
+
[searchButton.widthAnchor constraintEqualToConstant:LegendToolbarSearchCollapsedSize],
|
|
1534
|
+
[searchButton.heightAnchor constraintEqualToConstant:LegendToolbarSearchCollapsedSize],
|
|
1535
|
+
]];
|
|
1536
|
+
|
|
1537
|
+
searchField = [[NSSearchField alloc] initWithFrame:NSMakeRect(0, 0, width, 28)];
|
|
1538
|
+
searchField.translatesAutoresizingMaskIntoConstraints = NO;
|
|
1539
|
+
searchField.hidden = value.length == 0;
|
|
1540
|
+
searchField.alphaValue = value.length > 0 ? 1 : 0;
|
|
1541
|
+
searchButton.hidden = value.length > 0;
|
|
1542
|
+
searchButton.alphaValue = value.length > 0 ? 0 : 1;
|
|
1543
|
+
[container addSubview:searchField];
|
|
1544
|
+
[NSLayoutConstraint activateConstraints:@[
|
|
1545
|
+
[searchField.leadingAnchor constraintEqualToAnchor:container.leadingAnchor],
|
|
1546
|
+
[searchField.trailingAnchor constraintEqualToAnchor:container.trailingAnchor],
|
|
1547
|
+
[searchField.topAnchor constraintEqualToAnchor:container.topAnchor],
|
|
1548
|
+
[searchField.bottomAnchor constraintEqualToAnchor:container.bottomAnchor],
|
|
1549
|
+
]];
|
|
1550
|
+
toolbarItem = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
|
|
1551
|
+
toolbarItem.view = container;
|
|
1552
|
+
objc_setAssociatedObject(toolbarItem, &LegendToolbarSearchFieldKey, searchField, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
1553
|
+
objc_setAssociatedObject(toolbarItem, &LegendToolbarSearchWidthKey, @(width), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
1554
|
+
objc_setAssociatedObject(toolbarItem, &LegendToolbarSearchButtonKey, searchButton, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
1555
|
+
objc_setAssociatedObject(toolbarItem, &LegendToolbarSearchContainerKey, container, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
1556
|
+
objc_setAssociatedObject(toolbarItem, &LegendToolbarSearchWidthConstraintKey, containerWidthConstraint, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
1557
|
+
objc_setAssociatedObject(searchButton, &LegendToolbarSearchItemKey, toolbarItem, OBJC_ASSOCIATION_ASSIGN);
|
|
1558
|
+
objc_setAssociatedObject(searchField, &LegendToolbarSearchItemKey, toolbarItem, OBJC_ASSOCIATION_ASSIGN);
|
|
1559
|
+
} else if (@available(macOS 11.0, *)) {
|
|
1560
|
+
NSSearchToolbarItem *searchToolbarItem = [[NSSearchToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
|
|
1561
|
+
searchField = searchToolbarItem.searchField;
|
|
1562
|
+
toolbarItem = searchToolbarItem;
|
|
1563
|
+
} else {
|
|
1564
|
+
searchField = [[NSSearchField alloc] initWithFrame:NSMakeRect(0, 0, width, 28)];
|
|
1565
|
+
toolbarItem = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
|
|
1566
|
+
toolbarItem.view = searchField;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
searchField.placeholderString = placeholder;
|
|
1570
|
+
searchField.stringValue = value;
|
|
1571
|
+
searchField.delegate = self;
|
|
1572
|
+
searchField.target = self;
|
|
1573
|
+
searchField.action = @selector(toolbarSearchSubmitted:);
|
|
1574
|
+
searchField.enabled = LegendDictionaryHasKey(config, @"enabled") ? [config[@"enabled"] boolValue] : YES;
|
|
1575
|
+
searchField.controlSize = NSControlSizeRegular;
|
|
1576
|
+
objc_setAssociatedObject(searchField, &LegendToolbarControlMetadataKey, metadata, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
1577
|
+
|
|
1578
|
+
toolbarItem.label = label;
|
|
1579
|
+
toolbarItem.paletteLabel = label;
|
|
1580
|
+
toolbarItem.toolTip = placeholder;
|
|
1581
|
+
toolbarItem.enabled = searchField.enabled;
|
|
1582
|
+
if (collapses) {
|
|
1583
|
+
searchField.enabled = toolbarItem.enabled;
|
|
1584
|
+
} else {
|
|
1585
|
+
toolbarItem.minSize = NSMakeSize(MIN(width, 120), 28);
|
|
1586
|
+
toolbarItem.maxSize = NSMakeSize(width, 28);
|
|
1587
|
+
}
|
|
1588
|
+
return toolbarItem;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
if (![type isEqualToString:@"segmented"]) {
|
|
1592
|
+
return nil;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
NSArray *segments = [config[@"segments"] isKindOfClass:NSArray.class] ? config[@"segments"] : @[];
|
|
1596
|
+
NSMutableArray<NSString *> *labels = [NSMutableArray new];
|
|
1597
|
+
NSMutableArray<NSString *> *systemImageNames = [NSMutableArray new];
|
|
1598
|
+
NSMutableArray<NSString *> *values = [NSMutableArray new];
|
|
1599
|
+
NSInteger selectedSegment = -1;
|
|
1600
|
+
NSString *selectedValue = [config[@"selectedValue"] isKindOfClass:NSString.class] ? config[@"selectedValue"] : nil;
|
|
1601
|
+
|
|
1602
|
+
for (id segment in segments) {
|
|
1603
|
+
if ([segment isKindOfClass:NSDictionary.class]) {
|
|
1604
|
+
NSDictionary *segmentConfig = (NSDictionary *)segment;
|
|
1605
|
+
NSString *label = [segmentConfig[@"label"] isKindOfClass:NSString.class] ? segmentConfig[@"label"] : @"";
|
|
1606
|
+
NSString *value = [segmentConfig[@"value"] isKindOfClass:NSString.class] ? segmentConfig[@"value"] : label;
|
|
1607
|
+
if (label.length > 0 && value != nil) {
|
|
1608
|
+
NSString *systemImageName = [segmentConfig[@"systemImageName"] isKindOfClass:NSString.class]
|
|
1609
|
+
? segmentConfig[@"systemImageName"]
|
|
1610
|
+
: @"";
|
|
1611
|
+
if ([value isEqualToString:selectedValue]) {
|
|
1612
|
+
selectedSegment = values.count;
|
|
1613
|
+
}
|
|
1614
|
+
[labels addObject:label];
|
|
1615
|
+
[systemImageNames addObject:systemImageName];
|
|
1616
|
+
[values addObject:value];
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
if (labels.count == 0) {
|
|
1622
|
+
return nil;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
NSSegmentedControl *control = [[NSSegmentedControl alloc] initWithFrame:NSZeroRect];
|
|
1626
|
+
control.segmentCount = labels.count;
|
|
1627
|
+
control.trackingMode = NSSegmentSwitchTrackingSelectOne;
|
|
1628
|
+
control.target = self;
|
|
1629
|
+
control.action = @selector(toolbarSegmentedControlChanged:);
|
|
1630
|
+
control.controlSize = NSControlSizeRegular;
|
|
1631
|
+
control.segmentStyle = NSSegmentStyleAutomatic;
|
|
1632
|
+
control.selectedSegment = selectedSegment;
|
|
1633
|
+
|
|
1634
|
+
CGFloat totalWidth = 0;
|
|
1635
|
+
for (NSInteger index = 0; index < labels.count; index += 1) {
|
|
1636
|
+
NSString *systemImageName = systemImageNames[index];
|
|
1637
|
+
NSImage *image = nil;
|
|
1638
|
+
if (systemImageName.length > 0) {
|
|
1639
|
+
if (@available(macOS 11.0, *)) {
|
|
1640
|
+
image = [NSImage imageWithSystemSymbolName:systemImageName accessibilityDescription:labels[index]];
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
if (image) {
|
|
1644
|
+
[control setImage:image forSegment:index];
|
|
1645
|
+
[control setToolTip:labels[index] forSegment:index];
|
|
1646
|
+
} else {
|
|
1647
|
+
[control setLabel:labels[index] forSegment:index];
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
NSFont *font = control.font ?: [NSFont systemFontOfSize:NSFont.systemFontSize];
|
|
1651
|
+
CGFloat width = image ? 38 : MAX(76, [labels[index] sizeWithAttributes:@{NSFontAttributeName: font}].width + 28);
|
|
1652
|
+
[control setWidth:width forSegment:index];
|
|
1653
|
+
totalWidth += width;
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
NSString *itemId = [config[@"id"] isKindOfClass:NSString.class] ? config[@"id"] : @"";
|
|
1657
|
+
objc_setAssociatedObject(control, &LegendToolbarControlMetadataKey, @{
|
|
1658
|
+
@"itemId": itemId,
|
|
1659
|
+
@"values": values,
|
|
1660
|
+
@"windowIdentifier": LegendToolbarWindowIdentifier(toolbar),
|
|
1661
|
+
@"instanceId": config[@"instanceId"] ?: @"",
|
|
1662
|
+
}, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
1663
|
+
control.frame = NSMakeRect(0, 0, totalWidth, control.fittingSize.height);
|
|
1664
|
+
|
|
1665
|
+
NSToolbarItem *toolbarItem = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
|
|
1666
|
+
toolbarItem.view = control;
|
|
1667
|
+
toolbarItem.label = [config[@"label"] isKindOfClass:NSString.class] ? config[@"label"] : itemId;
|
|
1668
|
+
toolbarItem.paletteLabel = toolbarItem.label;
|
|
1669
|
+
return toolbarItem;
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
- (void)toolbarMenuSliderChanged:(NSSlider *)sender
|
|
1673
|
+
{
|
|
1674
|
+
NSDictionary *metadata = objc_getAssociatedObject(sender, &LegendToolbarControlMetadataKey);
|
|
1675
|
+
double value = sender.doubleValue;
|
|
1676
|
+
NSTextField *readout = [sender.superview viewWithTag:731];
|
|
1677
|
+
readout.stringValue = [NSString stringWithFormat:@"%g%@", value, metadata[@"suffix"] ?: @""];
|
|
1678
|
+
[self sendWindowEventWithName:@"onToolbarItemSelected" body:@{
|
|
1679
|
+
@"identifier": metadata[@"windowIdentifier"], @"itemId": metadata[@"itemId"],
|
|
1680
|
+
@"instanceId": metadata[@"instanceId"] ?: @"",
|
|
1681
|
+
@"action": @{ @"type": @"valueChanged", @"itemId": metadata[@"value"], @"value": @(value) },
|
|
1682
|
+
}];
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
- (void)toolbarMenuItemSelected:(NSMenuItem *)sender
|
|
1686
|
+
{
|
|
1687
|
+
NSDictionary *representedObject = [sender.representedObject isKindOfClass:NSDictionary.class]
|
|
1688
|
+
? sender.representedObject
|
|
1689
|
+
: @{};
|
|
1690
|
+
NSString *identifier = [representedObject[@"windowIdentifier"] isKindOfClass:NSString.class]
|
|
1691
|
+
? representedObject[@"windowIdentifier"]
|
|
1692
|
+
: @"";
|
|
1693
|
+
NSString *itemId = [representedObject[@"itemId"] isKindOfClass:NSString.class]
|
|
1694
|
+
? representedObject[@"itemId"]
|
|
1695
|
+
: @"";
|
|
1696
|
+
NSString *value = [representedObject[@"value"] isKindOfClass:NSString.class]
|
|
1697
|
+
? representedObject[@"value"]
|
|
1698
|
+
: @"";
|
|
1699
|
+
|
|
1700
|
+
[self sendWindowEventWithName:@"onToolbarItemSelected"
|
|
1701
|
+
body:@{@"identifier": identifier, @"itemId": itemId, @"action": @{ @"type": @"action", @"itemId": value }, @"instanceId": representedObject[@"instanceId"] ?: @""}];
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
- (void)toolbarSearchButtonPressed:(id)sender
|
|
1705
|
+
{
|
|
1706
|
+
NSToolbarItem *toolbarItem = [sender isKindOfClass:NSToolbarItem.class]
|
|
1707
|
+
? (NSToolbarItem *)sender
|
|
1708
|
+
: nil;
|
|
1709
|
+
if (!toolbarItem) {
|
|
1710
|
+
id associatedItem = objc_getAssociatedObject(sender, &LegendToolbarSearchItemKey);
|
|
1711
|
+
if ([associatedItem isKindOfClass:NSToolbarItem.class]) {
|
|
1712
|
+
toolbarItem = (NSToolbarItem *)associatedItem;
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
[self expandToolbarSearchItem:toolbarItem value:nil focus:YES sendChange:YES animated:YES];
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
- (void)toolbarButtonItemPressed:(id)sender
|
|
1720
|
+
{
|
|
1721
|
+
id metadata = objc_getAssociatedObject(sender, &LegendToolbarControlMetadataKey);
|
|
1722
|
+
NSDictionary *representedObject = [metadata isKindOfClass:NSDictionary.class]
|
|
1723
|
+
? metadata
|
|
1724
|
+
: @{};
|
|
1725
|
+
NSString *identifier = [representedObject[@"windowIdentifier"] isKindOfClass:NSString.class]
|
|
1726
|
+
? representedObject[@"windowIdentifier"]
|
|
1727
|
+
: @"";
|
|
1728
|
+
NSString *itemId = [representedObject[@"itemId"] isKindOfClass:NSString.class]
|
|
1729
|
+
? representedObject[@"itemId"]
|
|
1730
|
+
: @"";
|
|
1731
|
+
NSString *value = [representedObject[@"value"] isKindOfClass:NSString.class]
|
|
1732
|
+
? representedObject[@"value"]
|
|
1733
|
+
: @"";
|
|
1734
|
+
NSArray *menuItems = [representedObject[@"menuItems"] isKindOfClass:NSArray.class]
|
|
1735
|
+
? representedObject[@"menuItems"]
|
|
1736
|
+
: @[];
|
|
1737
|
+
|
|
1738
|
+
if (menuItems.count > 0) {
|
|
1739
|
+
NSMenu *menu = [NSMenu new];
|
|
1740
|
+
menu.autoenablesItems = NO;
|
|
1741
|
+
for (id itemCandidate in menuItems) {
|
|
1742
|
+
if (![itemCandidate isKindOfClass:NSDictionary.class]) {
|
|
1743
|
+
continue;
|
|
1744
|
+
}
|
|
1745
|
+
NSDictionary *itemConfig = (NSDictionary *)itemCandidate;
|
|
1746
|
+
if (LegendDictionaryHasKey(itemConfig, @"separator") && [itemConfig[@"separator"] boolValue]) {
|
|
1747
|
+
[menu addItem:NSMenuItem.separatorItem];
|
|
1748
|
+
continue;
|
|
1749
|
+
}
|
|
1750
|
+
NSString *itemLabel = [itemConfig[@"title"] isKindOfClass:NSString.class] ? itemConfig[@"title"] : @"";
|
|
1751
|
+
NSString *itemValue = [itemConfig[@"id"] isKindOfClass:NSString.class] ? itemConfig[@"id"] : @"";
|
|
1752
|
+
if (itemLabel.length == 0 || itemValue.length == 0) {
|
|
1753
|
+
continue;
|
|
1754
|
+
}
|
|
1755
|
+
NSDictionary *sliderConfig = [itemConfig[@"slider"] isKindOfClass:NSDictionary.class] ? itemConfig[@"slider"] : nil;
|
|
1756
|
+
if (sliderConfig) {
|
|
1757
|
+
double minimum = [sliderConfig[@"min"] doubleValue];
|
|
1758
|
+
double maximum = [sliderConfig[@"max"] doubleValue];
|
|
1759
|
+
double initial = [sliderConfig[@"value"] doubleValue];
|
|
1760
|
+
if (!isfinite(minimum) || !isfinite(maximum) || !isfinite(initial) || maximum <= minimum) continue;
|
|
1761
|
+
NSString *suffix = [sliderConfig[@"suffix"] isKindOfClass:NSString.class] ? sliderConfig[@"suffix"] : @"";
|
|
1762
|
+
NSView *container = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 280, 68)];
|
|
1763
|
+
NSTextField *label = [NSTextField labelWithString:itemLabel];
|
|
1764
|
+
label.frame = NSMakeRect(16, 42, 190, 18);
|
|
1765
|
+
[container addSubview:label];
|
|
1766
|
+
NSTextField *readout = [NSTextField labelWithString:@""];
|
|
1767
|
+
readout.tag = 731;
|
|
1768
|
+
readout.alignment = NSTextAlignmentRight;
|
|
1769
|
+
readout.frame = NSMakeRect(210, 42, 54, 18);
|
|
1770
|
+
[container addSubview:readout];
|
|
1771
|
+
NSSlider *slider = [NSSlider sliderWithValue:MAX(minimum, MIN(maximum, initial)) minValue:minimum maxValue:maximum target:self action:@selector(toolbarMenuSliderChanged:)];
|
|
1772
|
+
slider.frame = NSMakeRect(16, 12, 248, 24);
|
|
1773
|
+
slider.continuous = YES;
|
|
1774
|
+
slider.accessibilityLabel = itemLabel;
|
|
1775
|
+
slider.enabled = LegendDictionaryHasKey(itemConfig, @"enabled") ? [itemConfig[@"enabled"] boolValue] : YES;
|
|
1776
|
+
readout.stringValue = [NSString stringWithFormat:@"%g%@", slider.doubleValue, suffix];
|
|
1777
|
+
objc_setAssociatedObject(slider, &LegendToolbarControlMetadataKey, @{
|
|
1778
|
+
@"windowIdentifier": identifier, @"instanceId": representedObject[@"instanceId"] ?: @"", @"itemId": itemId, @"value": itemValue, @"suffix": suffix,
|
|
1779
|
+
}, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
1780
|
+
[container addSubview:slider];
|
|
1781
|
+
NSMenuItem *sliderItem = [NSMenuItem new];
|
|
1782
|
+
sliderItem.view = container;
|
|
1783
|
+
[menu addItem:sliderItem];
|
|
1784
|
+
continue;
|
|
1785
|
+
}
|
|
1786
|
+
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:itemLabel action:@selector(toolbarMenuItemSelected:) keyEquivalent:@""];
|
|
1787
|
+
item.target = self;
|
|
1788
|
+
item.enabled = LegendDictionaryHasKey(itemConfig, @"enabled") ? [itemConfig[@"enabled"] boolValue] : YES;
|
|
1789
|
+
item.state = LegendDictionaryHasKey(itemConfig, @"checked") && [itemConfig[@"checked"] boolValue] ? NSControlStateValueOn : NSControlStateValueOff;
|
|
1790
|
+
NSString *itemSystemImageName = [itemConfig[@"systemImageName"] isKindOfClass:NSString.class] ? itemConfig[@"systemImageName"] : nil;
|
|
1791
|
+
if (itemSystemImageName.length > 0) {
|
|
1792
|
+
if (@available(macOS 11.0, *)) {
|
|
1793
|
+
item.image = [NSImage imageWithSystemSymbolName:itemSystemImageName accessibilityDescription:itemLabel];
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
item.representedObject = @{
|
|
1797
|
+
@"itemId": itemId,
|
|
1798
|
+
@"value": itemValue,
|
|
1799
|
+
@"windowIdentifier": identifier, @"instanceId": representedObject[@"instanceId"] ?: @"",
|
|
1800
|
+
};
|
|
1801
|
+
[menu addItem:item];
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
if (menu.numberOfItems > 0) {
|
|
1805
|
+
if ([sender isKindOfClass:NSView.class]) {
|
|
1806
|
+
NSView *senderView = (NSView *)sender;
|
|
1807
|
+
[menu popUpMenuPositioningItem:nil atLocation:NSMakePoint(0, NSHeight(senderView.bounds) + 4) inView:senderView];
|
|
1808
|
+
} else {
|
|
1809
|
+
NSEvent *event = NSApp.currentEvent;
|
|
1810
|
+
NSWindow *window = (NSWindow *)self.windows[identifier];
|
|
1811
|
+
if (event && window.contentView) {
|
|
1812
|
+
[NSMenu popUpContextMenu:menu withEvent:event forView:window.contentView];
|
|
1813
|
+
} else {
|
|
1814
|
+
[menu popUpMenuPositioningItem:nil atLocation:NSZeroPoint inView:nil];
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
return;
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
[self sendWindowEventWithName:@"onToolbarItemSelected"
|
|
1822
|
+
body:@{@"identifier": identifier, @"itemId": itemId, @"value": value, @"instanceId": representedObject[@"instanceId"] ?: @""}];
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
- (void)toolbarSegmentedControlChanged:(NSSegmentedControl *)sender
|
|
1826
|
+
{
|
|
1827
|
+
id metadata = objc_getAssociatedObject(sender, &LegendToolbarControlMetadataKey);
|
|
1828
|
+
NSDictionary *representedObject = [metadata isKindOfClass:NSDictionary.class]
|
|
1829
|
+
? metadata
|
|
1830
|
+
: @{};
|
|
1831
|
+
NSArray *values = [representedObject[@"values"] isKindOfClass:NSArray.class] ? representedObject[@"values"] : @[];
|
|
1832
|
+
NSInteger selectedSegment = sender.selectedSegment;
|
|
1833
|
+
NSString *value = selectedSegment >= 0 && selectedSegment < values.count && [values[selectedSegment] isKindOfClass:NSString.class]
|
|
1834
|
+
? values[selectedSegment]
|
|
1835
|
+
: @"";
|
|
1836
|
+
NSString *identifier = [representedObject[@"windowIdentifier"] isKindOfClass:NSString.class]
|
|
1837
|
+
? representedObject[@"windowIdentifier"]
|
|
1838
|
+
: @"";
|
|
1839
|
+
NSString *itemId = [representedObject[@"itemId"] isKindOfClass:NSString.class]
|
|
1840
|
+
? representedObject[@"itemId"]
|
|
1841
|
+
: @"";
|
|
1842
|
+
|
|
1843
|
+
[self sendWindowEventWithName:@"onToolbarItemSelected"
|
|
1844
|
+
body:@{@"identifier": identifier, @"itemId": itemId, @"value": value, @"kind": @"selection", @"instanceId": representedObject[@"instanceId"] ?: @""}];
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
- (void)sendToolbarSearchEventForField:(NSSearchField *)searchField
|
|
1848
|
+
submitted:(BOOL)submitted
|
|
1849
|
+
shiftKey:(BOOL)shiftKey
|
|
1850
|
+
{
|
|
1851
|
+
id metadata = objc_getAssociatedObject(searchField, &LegendToolbarControlMetadataKey);
|
|
1852
|
+
NSDictionary *representedObject = [metadata isKindOfClass:NSDictionary.class]
|
|
1853
|
+
? metadata
|
|
1854
|
+
: @{};
|
|
1855
|
+
NSString *identifier = [representedObject[@"windowIdentifier"] isKindOfClass:NSString.class]
|
|
1856
|
+
? representedObject[@"windowIdentifier"]
|
|
1857
|
+
: @"";
|
|
1858
|
+
NSString *itemId = [representedObject[@"itemId"] isKindOfClass:NSString.class]
|
|
1859
|
+
? representedObject[@"itemId"]
|
|
1860
|
+
: @"";
|
|
1861
|
+
|
|
1862
|
+
[self sendWindowEventWithName:@"onToolbarSearch"
|
|
1863
|
+
body:@{
|
|
1864
|
+
@"identifier": identifier,
|
|
1865
|
+
@"itemId": itemId,
|
|
1866
|
+
@"shiftKey": @(shiftKey),
|
|
1867
|
+
@"submitted": @(submitted),
|
|
1868
|
+
@"instanceId": representedObject[@"instanceId"] ?: @"",
|
|
1869
|
+
@"value": searchField.stringValue ?: @"",
|
|
1870
|
+
}];
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
- (void)controlTextDidChange:(NSNotification *)notification
|
|
1874
|
+
{
|
|
1875
|
+
if ([notification.object isKindOfClass:NSSearchField.class]) {
|
|
1876
|
+
[self sendToolbarSearchEventForField:(NSSearchField *)notification.object submitted:NO shiftKey:NO];
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector
|
|
1881
|
+
{
|
|
1882
|
+
if ([control isKindOfClass:NSSearchField.class] && commandSelector == @selector(cancelOperation:)) {
|
|
1883
|
+
[control.window makeFirstResponder:nil];
|
|
1884
|
+
return YES;
|
|
1885
|
+
}
|
|
1886
|
+
return NO;
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
- (void)controlTextDidEndEditing:(NSNotification *)notification
|
|
1890
|
+
{
|
|
1891
|
+
if ([notification.object isKindOfClass:NSSearchField.class]) {
|
|
1892
|
+
NSSearchField *searchField = (NSSearchField *)notification.object;
|
|
1893
|
+
if (searchField.stringValue.length == 0) {
|
|
1894
|
+
id associatedItem = objc_getAssociatedObject(searchField, &LegendToolbarSearchItemKey);
|
|
1895
|
+
if ([associatedItem isKindOfClass:NSToolbarItem.class]) {
|
|
1896
|
+
[self collapseToolbarSearchItem:(NSToolbarItem *)associatedItem animated:YES];
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
- (void)toolbarSearchSubmitted:(NSSearchField *)sender
|
|
1903
|
+
{
|
|
1904
|
+
NSEvent *currentEvent = NSApp.currentEvent;
|
|
1905
|
+
BOOL shiftKey = currentEvent && ((currentEvent.modifierFlags & NSEventModifierFlagShift) != 0);
|
|
1906
|
+
[self sendToolbarSearchEventForField:sender submitted:YES shiftKey:shiftKey];
|
|
1907
|
+
}
|
|
1908
|
+
#endif
|
|
1909
|
+
|
|
1910
|
+
- (NSString *)successJson
|
|
1911
|
+
{
|
|
1912
|
+
return [self jsonStringFromObject:@{@"success": @YES}];
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
- (NSString *)failureJson:(NSString *)message
|
|
1916
|
+
{
|
|
1917
|
+
return [self jsonStringFromObject:@{@"success": @NO, @"message": message ?: @""}];
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
- (NSString *)getConstantsJson
|
|
1921
|
+
{
|
|
1922
|
+
#if TARGET_OS_OSX
|
|
1923
|
+
return [self jsonStringFromObject:@{
|
|
1924
|
+
@"STYLE_MASK_BORDERLESS": @(NSWindowStyleMaskBorderless),
|
|
1925
|
+
@"STYLE_MASK_TITLED": @(NSWindowStyleMaskTitled),
|
|
1926
|
+
@"STYLE_MASK_CLOSABLE": @(NSWindowStyleMaskClosable),
|
|
1927
|
+
@"STYLE_MASK_MINIATURIZABLE": @(NSWindowStyleMaskMiniaturizable),
|
|
1928
|
+
@"STYLE_MASK_RESIZABLE": @(NSWindowStyleMaskResizable),
|
|
1929
|
+
@"STYLE_MASK_UNIFIED_TITLE_AND_TOOLBAR": @(NSWindowStyleMaskUnifiedTitleAndToolbar),
|
|
1930
|
+
@"STYLE_MASK_FULL_SCREEN": @(NSWindowStyleMaskFullScreen),
|
|
1931
|
+
@"STYLE_MASK_FULL_SIZE_CONTENT_VIEW": @(NSWindowStyleMaskFullSizeContentView),
|
|
1932
|
+
@"STYLE_MASK_UTILITY_WINDOW": @(NSWindowStyleMaskUtilityWindow),
|
|
1933
|
+
@"STYLE_MASK_DOC_MODAL_WINDOW": @(NSWindowStyleMaskDocModalWindow),
|
|
1934
|
+
@"STYLE_MASK_NONACTIVATING_PANEL": @(NSWindowStyleMaskNonactivatingPanel),
|
|
1935
|
+
@"WINDOW_LEVEL_NORMAL": @(NSNormalWindowLevel),
|
|
1936
|
+
@"WINDOW_LEVEL_FLOATING": @(NSFloatingWindowLevel),
|
|
1937
|
+
@"WINDOW_LEVEL_MODAL_PANEL": @(NSModalPanelWindowLevel),
|
|
1938
|
+
@"WINDOW_LEVEL_MAIN_MENU": @(NSMainMenuWindowLevel),
|
|
1939
|
+
@"WINDOW_LEVEL_STATUS": @(NSStatusWindowLevel),
|
|
1940
|
+
@"WINDOW_LEVEL_SCREEN_SAVER": @(NSScreenSaverWindowLevel),
|
|
1941
|
+
}];
|
|
1942
|
+
#else
|
|
1943
|
+
return @"{}";
|
|
1944
|
+
#endif
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
- (NSString *)getReactNativeStartupTimingJson
|
|
1948
|
+
{
|
|
1949
|
+
facebook::react::ReactMarker::StartupLogger& startupLogger =
|
|
1950
|
+
facebook::react::ReactMarker::StartupLogger::getInstance();
|
|
1951
|
+
NSMutableDictionary<NSString *, NSNumber *> *timing = [NSMutableDictionary new];
|
|
1952
|
+
|
|
1953
|
+
#if TARGET_OS_OSX
|
|
1954
|
+
timing[@"clockOffsetMs"] = @(
|
|
1955
|
+
NSDate.date.timeIntervalSince1970 * 1000 - CACurrentMediaTime() * 1000
|
|
1956
|
+
);
|
|
1957
|
+
#if __has_include(<RNDesktopApp/SparkDesktop.h>)
|
|
1958
|
+
[timing addEntriesFromDictionary:SparkStartupTiming()];
|
|
1959
|
+
#else
|
|
1960
|
+
if (LegendMainWindowFirstVisibleTimeMs > 0) {
|
|
1961
|
+
timing[@"mainWindowFirstVisibleTime"] = @(LegendMainWindowFirstVisibleTimeMs);
|
|
1962
|
+
}
|
|
1963
|
+
if (LegendMainWindowReactRootAttachedTimeMs > 0) {
|
|
1964
|
+
timing[@"mainWindowReactRootAttachedTime"] = @(LegendMainWindowReactRootAttachedTimeMs);
|
|
1965
|
+
}
|
|
1966
|
+
#endif
|
|
1967
|
+
#endif
|
|
1968
|
+
|
|
1969
|
+
double startTime = startupLogger.getAppStartupStartTime();
|
|
1970
|
+
if (std::isnan(startTime)) {
|
|
1971
|
+
startTime = startupLogger.getInitReactRuntimeStartTime();
|
|
1972
|
+
}
|
|
1973
|
+
if (!std::isnan(startTime)) {
|
|
1974
|
+
timing[@"startTime"] = @(startTime);
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
const struct {
|
|
1978
|
+
NSString *key;
|
|
1979
|
+
double value;
|
|
1980
|
+
} markers[] = {
|
|
1981
|
+
{@"initializeRuntimeStart", startupLogger.getInitReactRuntimeStartTime()},
|
|
1982
|
+
{@"executeJavaScriptBundleEntryPointStart", startupLogger.getRunJSBundleStartTime()},
|
|
1983
|
+
{@"executeJavaScriptBundleEntryPointEnd", startupLogger.getRunJSBundleEndTime()},
|
|
1984
|
+
{@"initializeRuntimeEnd", startupLogger.getInitReactRuntimeEndTime()},
|
|
1985
|
+
{@"endTime", startupLogger.getAppStartupEndTime()},
|
|
1986
|
+
};
|
|
1987
|
+
for (const auto& marker : markers) {
|
|
1988
|
+
if (!std::isnan(marker.value)) {
|
|
1989
|
+
timing[marker.key] = @(marker.value);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
return [self jsonStringFromObject:timing];
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
- (NSArray<NSDictionary *> *)displayDictionaries
|
|
1997
|
+
{
|
|
1998
|
+
#if TARGET_OS_OSX
|
|
1999
|
+
NSMutableArray<NSDictionary *> *displays = [NSMutableArray new];
|
|
2000
|
+
NSScreen *mainScreen = NSScreen.mainScreen;
|
|
2001
|
+
for (NSScreen *screen in NSScreen.screens) {
|
|
2002
|
+
NSNumber *screenNumber = screen.deviceDescription[@"NSScreenNumber"];
|
|
2003
|
+
NSRect frame = screen.frame;
|
|
2004
|
+
NSRect visibleFrame = screen.visibleFrame;
|
|
2005
|
+
NSString *name = @"Display";
|
|
2006
|
+
if (@available(macOS 10.15, *)) {
|
|
2007
|
+
name = screen.localizedName ?: name;
|
|
2008
|
+
}
|
|
2009
|
+
CFUUIDRef uuid = screenNumber ? CGDisplayCreateUUIDFromDisplayID(screenNumber.unsignedIntValue) : nullptr;
|
|
2010
|
+
NSString *persistentId = uuid ? CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuid)) : screenNumber.stringValue;
|
|
2011
|
+
if (uuid) CFRelease(uuid);
|
|
2012
|
+
[displays addObject:@{
|
|
2013
|
+
@"persistentId": persistentId ?: @"",
|
|
2014
|
+
@"id": screenNumber.stringValue ?: @"",
|
|
2015
|
+
@"name": name,
|
|
2016
|
+
@"isMain": @(screen == mainScreen),
|
|
2017
|
+
@"scaleFactor": @(screen.backingScaleFactor),
|
|
2018
|
+
@"frame": @{ @"x": @(frame.origin.x), @"y": @(frame.origin.y), @"width": @(frame.size.width), @"height": @(frame.size.height) },
|
|
2019
|
+
@"visibleFrame": @{ @"x": @(visibleFrame.origin.x), @"y": @(visibleFrame.origin.y), @"width": @(visibleFrame.size.width), @"height": @(visibleFrame.size.height) },
|
|
2020
|
+
}];
|
|
2021
|
+
}
|
|
2022
|
+
return displays;
|
|
2023
|
+
#else
|
|
2024
|
+
return @[];
|
|
2025
|
+
#endif
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
- (void)getDisplays:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
|
|
2029
|
+
{
|
|
2030
|
+
resolve([self jsonStringFromObject:[self displayDictionaries]]);
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
#if TARGET_OS_OSX
|
|
2034
|
+
- (RCTUIView *)createReactRootViewWithModuleName:(NSString *)moduleName initialProperties:(NSDictionary *)initialProps
|
|
2035
|
+
{
|
|
2036
|
+
|
|
2037
|
+
id appDelegate = NSApplication.sharedApplication.delegate;
|
|
2038
|
+
RCTRootViewFactory *rootViewFactory = nil;
|
|
2039
|
+
|
|
2040
|
+
if ([appDelegate respondsToSelector:@selector(rootViewFactory)]) {
|
|
2041
|
+
rootViewFactory = [(id<RNWindowManagerRootViewFactoryProvider>)appDelegate rootViewFactory];
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
if (rootViewFactory) {
|
|
2045
|
+
RCTUIView *rootView = (RCTUIView *)[rootViewFactory viewWithModuleName:moduleName initialProperties:initialProps];
|
|
2046
|
+
return rootView;
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
RCTBridge *bridge = self.bridge;
|
|
2050
|
+
if (!bridge) {
|
|
2051
|
+
return nil;
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
RCTUIView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:moduleName initialProperties:initialProps];
|
|
2055
|
+
return rootView;
|
|
2056
|
+
}
|
|
2057
|
+
#endif
|
|
2058
|
+
|
|
2059
|
+
- (void)finishWindowRestoration:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
|
|
2060
|
+
{
|
|
2061
|
+
#if TARGET_OS_OSX
|
|
2062
|
+
RCTExecuteOnMainQueue(^{
|
|
2063
|
+
// Adopted windows are removed from this collection by LegendTakePrecreatedWindow.
|
|
2064
|
+
// Close only unclaimed native shells; preserve saved records and document data.
|
|
2065
|
+
for (NSString *identifier in LegendPrecreatedWindows.allKeys) {
|
|
2066
|
+
LegendRemovePrecreatedWindow(identifier);
|
|
2067
|
+
}
|
|
2068
|
+
resolve([self successJson]);
|
|
2069
|
+
});
|
|
2070
|
+
#else
|
|
2071
|
+
resolve([self failureJson:@"WindowManager is only available on macOS"]);
|
|
2072
|
+
#endif
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
- (void)openWindow:(NSString *)optionsJson resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
|
|
2076
|
+
{
|
|
2077
|
+
#if TARGET_OS_OSX
|
|
2078
|
+
RCTExecuteOnMainQueue(^{
|
|
2079
|
+
NSDictionary *options = [self parseObjectJSON:optionsJson];
|
|
2080
|
+
NSString *identifier = [options[@"identifier"] isKindOfClass:NSString.class] ? options[@"identifier"] : nil;
|
|
2081
|
+
NSString *moduleName = [options[@"moduleName"] isKindOfClass:NSString.class] ? options[@"moduleName"] : nil;
|
|
2082
|
+
NSAppearance *darkAppearance = LegendDarkAppearance();
|
|
2083
|
+
|
|
2084
|
+
if (moduleName.length == 0) {
|
|
2085
|
+
moduleName = identifier;
|
|
2086
|
+
}
|
|
2087
|
+
if (identifier.length == 0) {
|
|
2088
|
+
identifier = moduleName ?: @"default";
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
// A caller can turn off restoration before the window has been adopted by
|
|
2092
|
+
// React, so remove a native startup shell immediately when requested.
|
|
2093
|
+
if (LegendDictionaryHasKey(options, @"restoreOnLaunch") && ![options[@"restoreOnLaunch"] boolValue]) {
|
|
2094
|
+
LegendSetRestorableWindowOptions(identifier, nil);
|
|
2095
|
+
LegendRemovePrecreatedWindow(identifier);
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
NSString *title = [options[@"title"] isKindOfClass:NSString.class] ? options[@"title"] : nil;
|
|
2099
|
+
title = title ?: moduleName ?: @"New Window";
|
|
2100
|
+
BOOL hasRepresentedURL = LegendDictionaryHasKey(options, @"representedURL");
|
|
2101
|
+
id representedURLValue = hasRepresentedURL ? options[@"representedURL"] : nil;
|
|
2102
|
+
|
|
2103
|
+
NSDictionary *windowStyle = [options[@"windowStyle"] isKindOfClass:NSDictionary.class] ? options[@"windowStyle"] : @{};
|
|
2104
|
+
NSNumber *maskNumber = [windowStyle[@"mask"] isKindOfClass:NSNumber.class] ? windowStyle[@"mask"] : nil;
|
|
2105
|
+
NSNumber *transparentTitlebar = [windowStyle[@"titlebarAppearsTransparent"] isKindOfClass:NSNumber.class]
|
|
2106
|
+
? windowStyle[@"titlebarAppearsTransparent"]
|
|
2107
|
+
: nil;
|
|
2108
|
+
NSString *titleVisibility = [windowStyle[@"titleVisibility"] isKindOfClass:NSString.class]
|
|
2109
|
+
? windowStyle[@"titleVisibility"]
|
|
2110
|
+
: nil;
|
|
2111
|
+
NSString *toolbarStyle = [windowStyle[@"toolbarStyle"] isKindOfClass:NSString.class] ? windowStyle[@"toolbarStyle"] : nil;
|
|
2112
|
+
NSString *contentLayoutMode = [windowStyle[@"contentLayoutMode"] isKindOfClass:NSString.class]
|
|
2113
|
+
? windowStyle[@"contentLayoutMode"]
|
|
2114
|
+
: nil;
|
|
2115
|
+
NSString *titlebarSeparatorStyle = [windowStyle[@"titlebarSeparatorStyle"] isKindOfClass:NSString.class]
|
|
2116
|
+
? windowStyle[@"titlebarSeparatorStyle"]
|
|
2117
|
+
: nil;
|
|
2118
|
+
NSString *backgroundColor = [windowStyle[@"backgroundColor"] isKindOfClass:NSString.class]
|
|
2119
|
+
? windowStyle[@"backgroundColor"]
|
|
2120
|
+
: nil;
|
|
2121
|
+
NSString *appearance = [windowStyle[@"appearance"] isKindOfClass:NSString.class]
|
|
2122
|
+
? windowStyle[@"appearance"]
|
|
2123
|
+
: nil;
|
|
2124
|
+
BOOL usesTitlebarBackground = transparentTitlebar.boolValue && backgroundColor.length > 0;
|
|
2125
|
+
NSNumber *levelNumber = [options[@"level"] isKindOfClass:NSNumber.class] ? options[@"level"] : nil;
|
|
2126
|
+
BOOL transparentBackground = [options[@"transparentBackground"] boolValue];
|
|
2127
|
+
BOOL interceptClose = [options[@"interceptClose"] boolValue];
|
|
2128
|
+
BOOL deferOrderFront = [options[@"deferOrderFront"] boolValue];
|
|
2129
|
+
NSNumber *hasShadowNumber = [options[@"hasShadow"] isKindOfClass:NSNumber.class] ? options[@"hasShadow"] : nil;
|
|
2130
|
+
BOOL shouldApplyHasShadow = hasShadowNumber != nil;
|
|
2131
|
+
BOOL hasShadow = shouldApplyHasShadow ? hasShadowNumber.boolValue : NO;
|
|
2132
|
+
NSNumber *animateFrameChangeNumber = [options[@"animateFrameChange"] isKindOfClass:NSNumber.class]
|
|
2133
|
+
? options[@"animateFrameChange"]
|
|
2134
|
+
: nil;
|
|
2135
|
+
BOOL animateFrameChange = animateFrameChangeNumber ? animateFrameChangeNumber.boolValue : NO;
|
|
2136
|
+
NSNumber *frameAnimationDurationNumber = [options[@"frameAnimationDurationMs"] isKindOfClass:NSNumber.class]
|
|
2137
|
+
? options[@"frameAnimationDurationMs"]
|
|
2138
|
+
: nil;
|
|
2139
|
+
NSTimeInterval frameAnimationDuration = frameAnimationDurationNumber ? frameAnimationDurationNumber.doubleValue / 1000.0 : 0;
|
|
2140
|
+
NSNumber *widthNumber = [windowStyle[@"width"] isKindOfClass:NSNumber.class]
|
|
2141
|
+
? windowStyle[@"width"]
|
|
2142
|
+
: ([options[@"width"] isKindOfClass:NSNumber.class] ? options[@"width"] : nil);
|
|
2143
|
+
NSNumber *heightNumber = [windowStyle[@"height"] isKindOfClass:NSNumber.class]
|
|
2144
|
+
? windowStyle[@"height"]
|
|
2145
|
+
: ([options[@"height"] isKindOfClass:NSNumber.class] ? options[@"height"] : nil);
|
|
2146
|
+
CGFloat width = widthNumber ? widthNumber.doubleValue : 400;
|
|
2147
|
+
CGFloat height = heightNumber ? heightNumber.doubleValue : 300;
|
|
2148
|
+
NSNumber *minWidthNumber = [windowStyle[@"minWidth"] isKindOfClass:NSNumber.class]
|
|
2149
|
+
? windowStyle[@"minWidth"]
|
|
2150
|
+
: ([options[@"minWidth"] isKindOfClass:NSNumber.class] ? options[@"minWidth"] : nil);
|
|
2151
|
+
NSNumber *minHeightNumber = [windowStyle[@"minHeight"] isKindOfClass:NSNumber.class]
|
|
2152
|
+
? windowStyle[@"minHeight"]
|
|
2153
|
+
: ([options[@"minHeight"] isKindOfClass:NSNumber.class] ? options[@"minHeight"] : nil);
|
|
2154
|
+
BOOL hasMinWidth = minWidthNumber != nil;
|
|
2155
|
+
BOOL hasMinHeight = minHeightNumber != nil;
|
|
2156
|
+
CGFloat minWidth = hasMinWidth ? minWidthNumber.doubleValue : 0;
|
|
2157
|
+
CGFloat minHeight = hasMinHeight ? minHeightNumber.doubleValue : 0;
|
|
2158
|
+
|
|
2159
|
+
if (hasMinWidth && width < minWidth) {
|
|
2160
|
+
width = minWidth;
|
|
2161
|
+
}
|
|
2162
|
+
if (hasMinHeight && height < minHeight) {
|
|
2163
|
+
height = minHeight;
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
NSNumber *originX = [options[@"x"] isKindOfClass:NSNumber.class] ? options[@"x"] : nil;
|
|
2167
|
+
NSNumber *originY = [options[@"y"] isKindOfClass:NSNumber.class] ? options[@"y"] : nil;
|
|
2168
|
+
BOOL hasToolbar = [windowStyle[@"hasToolbar"] boolValue];
|
|
2169
|
+
NSWindow *existingWindow = (NSWindow *)self.windows[identifier];
|
|
2170
|
+
if (existingWindow) { reject(@"E_ALREADY_EXISTS", @"Window ID is already open", nil); return; }
|
|
2171
|
+
|
|
2172
|
+
NSUInteger styleMask = maskNumber
|
|
2173
|
+
? maskNumber.unsignedIntegerValue
|
|
2174
|
+
: (NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable);
|
|
2175
|
+
NSWindow *precreatedWindow = LegendTakePrecreatedWindow ? LegendTakePrecreatedWindow(identifier) : nil;
|
|
2176
|
+
NSWindow *window = precreatedWindow ?: [[([options[@"sparkOverlay"] boolValue] || windowStyle[@"panelStyle"] ? NSPanel.class : NSWindow.class) alloc] initWithContentRect:NSMakeRect(0, 0, width, height)
|
|
2177
|
+
styleMask:styleMask
|
|
2178
|
+
backing:NSBackingStoreBuffered
|
|
2179
|
+
defer:NO];
|
|
2180
|
+
objc_setAssociatedObject(window, NSSelectorFromString(@"sparkWindowInstanceId"), NSUUID.UUID.UUIDString, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
2181
|
+
if ([options[@"sparkOverlay"] boolValue]) {
|
|
2182
|
+
((NSPanel *)window).hidesOnDeactivate = NO;
|
|
2183
|
+
window.styleMask |= NSWindowStyleMaskNonactivatingPanel;
|
|
2184
|
+
window.collectionBehavior = NSWindowCollectionBehaviorCanJoinAllSpaces | NSWindowCollectionBehaviorFullScreenAuxiliary;
|
|
2185
|
+
}
|
|
2186
|
+
objc_setAssociatedObject(window, NSSelectorFromString(@"sparkRestoredShell"), @(precreatedWindow != nil), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
2187
|
+
LegendApplyWindowOptions(window, options);
|
|
2188
|
+
LegendApplyContentLayoutModeOption(window, contentLayoutMode);
|
|
2189
|
+
LegendApplyContentLayoutMode(window, maskNumber, usesTitlebarBackground);
|
|
2190
|
+
|
|
2191
|
+
if (appearance) {
|
|
2192
|
+
LegendApplyWindowAppearance(window, appearance);
|
|
2193
|
+
} else if (darkAppearance) {
|
|
2194
|
+
window.appearance = darkAppearance;
|
|
2195
|
+
}
|
|
2196
|
+
window.releasedWhenClosed = NO;
|
|
2197
|
+
LegendApplyWindowTitleAndRepresentedURL(window, title, hasRepresentedURL, representedURLValue, title);
|
|
2198
|
+
if (transparentTitlebar != nil) {
|
|
2199
|
+
window.titlebarAppearsTransparent = transparentTitlebar.boolValue;
|
|
2200
|
+
}
|
|
2201
|
+
LegendApplyTitleVisibility(window, titleVisibility);
|
|
2202
|
+
if (hasToolbar) {
|
|
2203
|
+
NSToolbar *toolbar = LegendCreateToolbar(identifier);
|
|
2204
|
+
window.toolbar = toolbar;
|
|
2205
|
+
}
|
|
2206
|
+
LegendApplyToolbarStyle(window, toolbarStyle);
|
|
2207
|
+
LegendApplyTitlebarSeparatorStyle(window, titlebarSeparatorStyle);
|
|
2208
|
+
[self applyTitlebarControlsFromOptions:options toWindow:window identifier:identifier];
|
|
2209
|
+
[self applyToolbarItemsFromOptions:options toWindow:window identifier:identifier];
|
|
2210
|
+
[self applyTitlebarMaterialFromOptions:options toWindow:window identifier:identifier];
|
|
2211
|
+
LegendApplyWindowBackgroundColor(window, backgroundColor);
|
|
2212
|
+
if (levelNumber) {
|
|
2213
|
+
window.level = levelNumber.integerValue;
|
|
2214
|
+
}
|
|
2215
|
+
if (shouldApplyHasShadow) {
|
|
2216
|
+
window.hasShadow = hasShadow;
|
|
2217
|
+
if (hasShadow) {
|
|
2218
|
+
[window invalidateShadow];
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
if (transparentBackground) {
|
|
2222
|
+
window.opaque = NO;
|
|
2223
|
+
if (!hasToolbar) {
|
|
2224
|
+
window.backgroundColor = NSColor.clearColor;
|
|
2225
|
+
}
|
|
2226
|
+
window.contentView.wantsLayer = YES;
|
|
2227
|
+
window.contentView.layer.masksToBounds = NO;
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
if (precreatedWindow) {
|
|
2231
|
+
// Native startup already restored this window to its final AppKit frame.
|
|
2232
|
+
} else if (originX || originY) {
|
|
2233
|
+
NSPoint origin = window.frame.origin;
|
|
2234
|
+
if (originX) {
|
|
2235
|
+
origin.x = originX.doubleValue;
|
|
2236
|
+
}
|
|
2237
|
+
if (originY) {
|
|
2238
|
+
origin.y = originY.doubleValue;
|
|
2239
|
+
}
|
|
2240
|
+
[window setFrameOrigin:origin];
|
|
2241
|
+
} else {
|
|
2242
|
+
[window center];
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
if (hasMinWidth || hasMinHeight) {
|
|
2246
|
+
NSSize currentMinSize = window.minSize;
|
|
2247
|
+
[window setMinSize:NSMakeSize(hasMinWidth ? minWidth : currentMinSize.width,
|
|
2248
|
+
hasMinHeight ? minHeight : currentMinSize.height)];
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
NSColor *startupBackgroundColor = transparentBackground
|
|
2252
|
+
? NSColor.clearColor
|
|
2253
|
+
: LegendWindowStartupBackgroundColor(window, backgroundColor);
|
|
2254
|
+
if (!transparentBackground && backgroundColor.length == 0) {
|
|
2255
|
+
window.backgroundColor = startupBackgroundColor;
|
|
2256
|
+
window.opaque = startupBackgroundColor.alphaComponent >= 1;
|
|
2257
|
+
LegendApplyBackgroundColorToView(window.contentView, startupBackgroundColor);
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
if (!precreatedWindow && [options[@"restoreOnLaunch"] boolValue]) {
|
|
2261
|
+
[window setFrameAutosaveName:LegendManagedWindowFrameAutosaveName(identifier)];
|
|
2262
|
+
}
|
|
2263
|
+
if (!precreatedWindow && LegendPrepareSidebarSplitViewStartup) {
|
|
2264
|
+
LegendPrepareSidebarSplitViewStartup(window, windowStyle[@"startupSplitView"]);
|
|
2265
|
+
}
|
|
2266
|
+
BOOL presentedBeforeReactRoot = precreatedWindow != nil || !deferOrderFront;
|
|
2267
|
+
if (!deferOrderFront && !window.visible) {
|
|
2268
|
+
[window.contentView displayIfNeeded];
|
|
2269
|
+
[window displayIfNeeded];
|
|
2270
|
+
[window makeKeyAndOrderFront:nil];
|
|
2271
|
+
if (levelNumber) {
|
|
2272
|
+
[window orderFrontRegardless];
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
NSDictionary *initialProps = [self initialPropsFromOptions:options];
|
|
2277
|
+
RCTUIView *rootView = [self createReactRootViewWithModuleName:moduleName initialProperties:initialProps];
|
|
2278
|
+
if (!rootView) {
|
|
2279
|
+
if (presentedBeforeReactRoot) {
|
|
2280
|
+
[window close];
|
|
2281
|
+
}
|
|
2282
|
+
reject(@"no_root_view", @"React root view could not be created", nil);
|
|
2283
|
+
return;
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
rootView.backgroundColor = startupBackgroundColor;
|
|
2287
|
+
__weak NSWindow *weakWindow = window;
|
|
2288
|
+
void (^installRoot)(void) = ^{
|
|
2289
|
+
NSWindow *targetWindow = weakWindow;
|
|
2290
|
+
if (!targetWindow) {
|
|
2291
|
+
return;
|
|
2292
|
+
}
|
|
2293
|
+
[rootView removeFromSuperview];
|
|
2294
|
+
targetWindow.contentView = rootView;
|
|
2295
|
+
if (usesTitlebarBackground) {
|
|
2296
|
+
LegendEnsureRootViewContainer(targetWindow, rootView);
|
|
2297
|
+
}
|
|
2298
|
+
LegendApplyWindowBackgroundColor(targetWindow, backgroundColor);
|
|
2299
|
+
LegendSizeRootViewToWindow(rootView, targetWindow);
|
|
2300
|
+
};
|
|
2301
|
+
if (!LegendAttachSidebarSplitViewStartupRoot ||
|
|
2302
|
+
!LegendAttachSidebarSplitViewStartupRoot(window, rootView, installRoot)) {
|
|
2303
|
+
installRoot();
|
|
2304
|
+
}
|
|
2305
|
+
objc_setAssociatedObject(window, &LegendManagedRootViewKey, rootView, OBJC_ASSOCIATION_ASSIGN);
|
|
2306
|
+
LegendSizeRootViewToWindow(rootView, window);
|
|
2307
|
+
if (transparentBackground) {
|
|
2308
|
+
rootView.backgroundColor = NSColor.clearColor;
|
|
2309
|
+
window.contentView.wantsLayer = YES;
|
|
2310
|
+
window.contentView.layer.backgroundColor = NSColor.clearColor.CGColor;
|
|
2311
|
+
window.contentView.layer.masksToBounds = NO;
|
|
2312
|
+
}
|
|
2313
|
+
rootView.wantsLayer = YES;
|
|
2314
|
+
rootView.layerUsesCoreImageFilters = YES;
|
|
2315
|
+
if (!rootView.layer) {
|
|
2316
|
+
rootView.layer = [CALayer layer];
|
|
2317
|
+
}
|
|
2318
|
+
rootView.layer.masksToBounds = NO;
|
|
2319
|
+
LegendPrepareWindowForDisplay(window, backgroundColor);
|
|
2320
|
+
window.delegate = self;
|
|
2321
|
+
|
|
2322
|
+
window.identifier = identifier;
|
|
2323
|
+
self.windows[identifier] = window;
|
|
2324
|
+
self.rootViews[identifier] = rootView;
|
|
2325
|
+
self.moduleNames[identifier] = moduleName ?: @"";
|
|
2326
|
+
[self recordWindowOptions:options identifier:identifier moduleName:moduleName];
|
|
2327
|
+
if (interceptClose) {
|
|
2328
|
+
[self.closeRequestIdentifiers addObject:identifier];
|
|
2329
|
+
} else {
|
|
2330
|
+
[self.closeRequestIdentifiers removeObject:identifier];
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
[self finishOpeningWindow:window options:windowStyle resolve:resolve reject:reject];
|
|
2334
|
+
});
|
|
2335
|
+
#else
|
|
2336
|
+
resolve([self failureJson:@"WindowManager is only available on macOS"]);
|
|
2337
|
+
#endif
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
#if TARGET_OS_OSX
|
|
2341
|
+
// Opening resolves only after AppKit enters its fullscreen Space, so callers
|
|
2342
|
+
// can safely focus a presenter on another display afterwards.
|
|
2343
|
+
- (void)finishOpeningWindow:(NSWindow *)window options:(NSDictionary *)windowStyle
|
|
2344
|
+
resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
|
|
2345
|
+
{
|
|
2346
|
+
if (![windowStyle[@"fullscreen"] boolValue] || (window.styleMask & NSWindowStyleMaskFullScreen)) {
|
|
2347
|
+
resolve([self successJson]);
|
|
2348
|
+
return;
|
|
2349
|
+
}
|
|
2350
|
+
objc_setAssociatedObject(window, &LegendManagedFullscreenPresentationKey, @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
2351
|
+
window.collectionBehavior = (window.collectionBehavior & ~NSWindowCollectionBehaviorFullScreenAuxiliary)
|
|
2352
|
+
| NSWindowCollectionBehaviorFullScreenPrimary;
|
|
2353
|
+
__block id entered = nil;
|
|
2354
|
+
__block id closed = nil;
|
|
2355
|
+
__block BOOL finished = NO;
|
|
2356
|
+
void (^finish)(BOOL) = ^(BOOL success) {
|
|
2357
|
+
if (finished) return;
|
|
2358
|
+
finished = YES;
|
|
2359
|
+
[NSNotificationCenter.defaultCenter removeObserver:entered];
|
|
2360
|
+
[NSNotificationCenter.defaultCenter removeObserver:closed];
|
|
2361
|
+
entered = nil;
|
|
2362
|
+
closed = nil;
|
|
2363
|
+
if (success) resolve([self successJson]);
|
|
2364
|
+
else {
|
|
2365
|
+
[window close];
|
|
2366
|
+
reject(@"fullscreen_failed", @"Fullscreen did not complete. Check the selected display and try again.", nil);
|
|
2367
|
+
}
|
|
2368
|
+
};
|
|
2369
|
+
entered = [NSNotificationCenter.defaultCenter addObserverForName:NSWindowDidEnterFullScreenNotification
|
|
2370
|
+
object:window queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification *note) { finish(YES); }];
|
|
2371
|
+
closed = [NSNotificationCenter.defaultCenter addObserverForName:NSWindowWillCloseNotification
|
|
2372
|
+
object:window queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification *note) { finish(NO); }];
|
|
2373
|
+
[window makeKeyAndOrderFront:nil];
|
|
2374
|
+
[window toggleFullScreen:nil];
|
|
2375
|
+
// Bound failures where AppKit never sends the completion notification.
|
|
2376
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ finish(NO); });
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
- (NSApplicationPresentationOptions)window:(NSWindow *)window
|
|
2380
|
+
willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)options
|
|
2381
|
+
{
|
|
2382
|
+
if (![objc_getAssociatedObject(window, &LegendManagedFullscreenPresentationKey) boolValue]) return options;
|
|
2383
|
+
return options | NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationAutoHideDock
|
|
2384
|
+
| NSApplicationPresentationAutoHideToolbar;
|
|
2385
|
+
}
|
|
2386
|
+
#endif
|
|
2387
|
+
|
|
2388
|
+
- (void)setWindowTitle:(NSString *)identifier
|
|
2389
|
+
title:(NSString *)title
|
|
2390
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
2391
|
+
reject:(RCTPromiseRejectBlock)reject
|
|
2392
|
+
{
|
|
2393
|
+
#if TARGET_OS_OSX
|
|
2394
|
+
RCTExecuteOnMainQueue(^{
|
|
2395
|
+
NSString *targetIdentifier = [self normalizeIdentifier:identifier];
|
|
2396
|
+
NSWindow *window = [targetIdentifier isEqualToString:@"main"]
|
|
2397
|
+
? [RNWindowManager getMainWindow]
|
|
2398
|
+
: (NSWindow *)self.windows[targetIdentifier];
|
|
2399
|
+
if (!window) {
|
|
2400
|
+
reject(@"E_NOT_FOUND", @"Window not found", nil);
|
|
2401
|
+
return;
|
|
2402
|
+
}
|
|
2403
|
+
window.title = title ?: @"";
|
|
2404
|
+
resolve([self successJson]);
|
|
2405
|
+
});
|
|
2406
|
+
#else
|
|
2407
|
+
resolve([self failureJson:@"WindowManager is only available on macOS"]);
|
|
2408
|
+
#endif
|
|
2409
|
+
}
|
|
2410
|
+
|
|
2411
|
+
- (void)setWindowToolbarItemText:(NSString *)identifier
|
|
2412
|
+
itemId:(NSString *)itemId
|
|
2413
|
+
text:(NSString *)text
|
|
2414
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
2415
|
+
reject:(RCTPromiseRejectBlock)reject
|
|
2416
|
+
{
|
|
2417
|
+
#if TARGET_OS_OSX
|
|
2418
|
+
RCTExecuteOnMainQueue(^{
|
|
2419
|
+
NSString *targetIdentifier = [self normalizeIdentifier:identifier];
|
|
2420
|
+
NSWindow *window = (NSWindow *)self.windows[targetIdentifier];
|
|
2421
|
+
if (!window) {
|
|
2422
|
+
resolve([self failureJson:@"Window not found"]);
|
|
2423
|
+
return;
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
NSString *toolbarIdentifier = [NSString stringWithFormat:@"legend.toolbar.%@", itemId ?: @""];
|
|
2427
|
+
NSToolbarItem *toolbarItem = nil;
|
|
2428
|
+
for (NSToolbarItem *candidate in window.toolbar.items) {
|
|
2429
|
+
if ([candidate.itemIdentifier isEqualToString:toolbarIdentifier]) {
|
|
2430
|
+
toolbarItem = candidate;
|
|
2431
|
+
break;
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
if (!toolbarItem) {
|
|
2435
|
+
resolve([self failureJson:@"Toolbar item not found"]);
|
|
2436
|
+
return;
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
if ([toolbarItem.view isKindOfClass:NSTextField.class]) {
|
|
2440
|
+
((NSTextField *)toolbarItem.view).stringValue = text ?: @"";
|
|
2441
|
+
} else if ([toolbarItem.view isKindOfClass:NSButton.class]) {
|
|
2442
|
+
((NSButton *)toolbarItem.view).title = text ?: @"";
|
|
2443
|
+
toolbarItem.label = text ?: @"";
|
|
2444
|
+
toolbarItem.paletteLabel = toolbarItem.label;
|
|
2445
|
+
} else {
|
|
2446
|
+
resolve([self failureJson:@"Toolbar item does not support text"]);
|
|
2447
|
+
return;
|
|
2448
|
+
}
|
|
2449
|
+
NSMutableArray *configs = [self.toolbarItemConfigs[window.toolbar.identifier] mutableCopy];
|
|
2450
|
+
for (NSUInteger index = 0; index < configs.count; index++) {
|
|
2451
|
+
if (![[self toolbarItemIdentifierForConfig:configs[index]] isEqualToString:toolbarIdentifier]) continue;
|
|
2452
|
+
NSMutableDictionary *config = [configs[index] mutableCopy];
|
|
2453
|
+
config[[toolbarItem.view isKindOfClass:NSTextField.class] ? @"text" : @"label"] = text ?: @"";
|
|
2454
|
+
configs[index] = config;
|
|
2455
|
+
break;
|
|
2456
|
+
}
|
|
2457
|
+
self.toolbarItemConfigs[window.toolbar.identifier] = configs;
|
|
2458
|
+
resolve([self successJson]);
|
|
2459
|
+
});
|
|
2460
|
+
#else
|
|
2461
|
+
resolve([self failureJson:@"WindowManager is only available on macOS"]);
|
|
2462
|
+
#endif
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
- (void)setPreventDisplaySleep:(BOOL)enabled
|
|
2466
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
2467
|
+
reject:(__unused RCTPromiseRejectBlock)reject
|
|
2468
|
+
{
|
|
2469
|
+
#if TARGET_OS_OSX
|
|
2470
|
+
RCTExecuteOnMainQueue(^{
|
|
2471
|
+
if (enabled && !self.displaySleepActivity) {
|
|
2472
|
+
self.displaySleepActivity = [NSProcessInfo.processInfo
|
|
2473
|
+
beginActivityWithOptions:NSActivityIdleDisplaySleepDisabled
|
|
2474
|
+
reason:@"Legend Slides presentation in progress"];
|
|
2475
|
+
} else if (!enabled && self.displaySleepActivity) {
|
|
2476
|
+
[NSProcessInfo.processInfo endActivity:self.displaySleepActivity];
|
|
2477
|
+
self.displaySleepActivity = nil;
|
|
2478
|
+
}
|
|
2479
|
+
resolve([self successJson]);
|
|
2480
|
+
});
|
|
2481
|
+
#else
|
|
2482
|
+
resolve([self failureJson:@"WindowManager is only available on macOS"]);
|
|
2483
|
+
#endif
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
- (void)focusToolbarSearchItem:(NSString *)identifier
|
|
2487
|
+
itemId:(NSString *)itemId
|
|
2488
|
+
value:(NSString *)value
|
|
2489
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
2490
|
+
reject:(RCTPromiseRejectBlock)reject
|
|
2491
|
+
{
|
|
2492
|
+
#if TARGET_OS_OSX
|
|
2493
|
+
RCTExecuteOnMainQueue(^{
|
|
2494
|
+
NSString *targetIdentifier = [self normalizeIdentifier:identifier];
|
|
2495
|
+
NSWindow *window = [targetIdentifier isEqualToString:@"main"]
|
|
2496
|
+
? [RNWindowManager getMainWindow]
|
|
2497
|
+
: (NSWindow *)self.windows[targetIdentifier];
|
|
2498
|
+
if (!window) {
|
|
2499
|
+
reject(@"E_NOT_FOUND", @"Window not found", nil);
|
|
2500
|
+
return;
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
NSString *toolbarIdentifier = [NSString stringWithFormat:@"legend.toolbar.%@", itemId ?: @""];
|
|
2504
|
+
NSToolbarItem *toolbarItem = nil;
|
|
2505
|
+
for (NSToolbarItem *candidate in window.toolbar.items) {
|
|
2506
|
+
if ([candidate.itemIdentifier isEqualToString:toolbarIdentifier]) {
|
|
2507
|
+
toolbarItem = candidate;
|
|
2508
|
+
break;
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
if (!toolbarItem) {
|
|
2512
|
+
reject(@"E_NOT_FOUND", @"Toolbar search item not found", nil);
|
|
2513
|
+
return;
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
NSSearchField *searchField = nil;
|
|
2517
|
+
if (@available(macOS 11.0, *)) {
|
|
2518
|
+
if ([toolbarItem isKindOfClass:NSSearchToolbarItem.class]) {
|
|
2519
|
+
searchField = ((NSSearchToolbarItem *)toolbarItem).searchField;
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
if (!searchField && [toolbarItem.view isKindOfClass:NSSearchField.class]) {
|
|
2523
|
+
searchField = (NSSearchField *)toolbarItem.view;
|
|
2524
|
+
}
|
|
2525
|
+
if (!searchField) {
|
|
2526
|
+
id associatedField = objc_getAssociatedObject(toolbarItem, &LegendToolbarSearchFieldKey);
|
|
2527
|
+
if ([associatedField isKindOfClass:NSSearchField.class]) {
|
|
2528
|
+
[self expandToolbarSearchItem:toolbarItem value:value focus:YES sendChange:YES animated:YES];
|
|
2529
|
+
resolve([self successJson]);
|
|
2530
|
+
return;
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
if (!searchField) {
|
|
2534
|
+
reject(@"E_NOT_FOUND", @"Toolbar search field not found", nil);
|
|
2535
|
+
return;
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
if (value) searchField.stringValue = value;
|
|
2539
|
+
[self sendToolbarSearchEventForField:searchField submitted:NO shiftKey:NO];
|
|
2540
|
+
[window makeKeyAndOrderFront:nil];
|
|
2541
|
+
[window makeFirstResponder:searchField];
|
|
2542
|
+
resolve([self successJson]);
|
|
2543
|
+
});
|
|
2544
|
+
#else
|
|
2545
|
+
resolve([self failureJson:@"WindowManager is only available on macOS"]);
|
|
2546
|
+
#endif
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
- (void)closeWindow:(NSString *)identifier resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
|
|
2550
|
+
{
|
|
2551
|
+
#if TARGET_OS_OSX
|
|
2552
|
+
RCTExecuteOnMainQueue(^{
|
|
2553
|
+
NSString *targetIdentifier = [self normalizeIdentifier:identifier];
|
|
2554
|
+
NSWindow *window = (NSWindow *)self.windows[targetIdentifier];
|
|
2555
|
+
if (!window) {
|
|
2556
|
+
resolve([self failureJson:@"No window to close"]);
|
|
2557
|
+
return;
|
|
2558
|
+
}
|
|
2559
|
+
[self handleWindowClosedForIdentifier:targetIdentifier closeWindow:YES];
|
|
2560
|
+
resolve([self successJson]);
|
|
2561
|
+
});
|
|
2562
|
+
#else
|
|
2563
|
+
resolve([self failureJson:@"WindowManager is only available on macOS"]);
|
|
2564
|
+
#endif
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2567
|
+
- (void)closeFrontmostWindow:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
|
|
2568
|
+
{
|
|
2569
|
+
#if TARGET_OS_OSX
|
|
2570
|
+
RCTExecuteOnMainQueue(^{
|
|
2571
|
+
NSWindow *window = NSApp.keyWindow ?: NSApp.mainWindow;
|
|
2572
|
+
if (!window) {
|
|
2573
|
+
resolve([self failureJson:@"No window to close"]);
|
|
2574
|
+
return;
|
|
2575
|
+
}
|
|
2576
|
+
NSString *identifier = [self identifierForWindow:window];
|
|
2577
|
+
if (identifier) {
|
|
2578
|
+
[self handleWindowClosedForIdentifier:identifier closeWindow:YES];
|
|
2579
|
+
} else {
|
|
2580
|
+
[window performClose:nil];
|
|
2581
|
+
}
|
|
2582
|
+
resolve([self successJson]);
|
|
2583
|
+
});
|
|
2584
|
+
#else
|
|
2585
|
+
resolve([self failureJson:@"WindowManager is only available on macOS"]);
|
|
2586
|
+
#endif
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
- (void)showMainWindow:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
|
|
2590
|
+
{
|
|
2591
|
+
#if TARGET_OS_OSX
|
|
2592
|
+
RCTExecuteOnMainQueue(^{
|
|
2593
|
+
NSWindow *mainWindow = [RNWindowManager getMainWindow];
|
|
2594
|
+
if (!mainWindow) {
|
|
2595
|
+
resolve([self failureJson:@"Main window not found"]);
|
|
2596
|
+
return;
|
|
2597
|
+
}
|
|
2598
|
+
[NSApp activateIgnoringOtherApps:YES];
|
|
2599
|
+
[mainWindow makeKeyAndOrderFront:nil];
|
|
2600
|
+
resolve([self successJson]);
|
|
2601
|
+
});
|
|
2602
|
+
#else
|
|
2603
|
+
resolve([self failureJson:@"WindowManager is only available on macOS"]);
|
|
2604
|
+
#endif
|
|
2605
|
+
}
|
|
2606
|
+
|
|
2607
|
+
- (void)hideMainWindow:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
|
|
2608
|
+
{
|
|
2609
|
+
#if TARGET_OS_OSX
|
|
2610
|
+
RCTExecuteOnMainQueue(^{
|
|
2611
|
+
NSWindow *mainWindow = [RNWindowManager getMainWindow];
|
|
2612
|
+
if (!mainWindow) {
|
|
2613
|
+
resolve([self failureJson:@"Main window not found"]);
|
|
2614
|
+
return;
|
|
2615
|
+
}
|
|
2616
|
+
[mainWindow orderOut:nil];
|
|
2617
|
+
[NSUserDefaults.standardUserDefaults removeObjectForKey:LegendMainWindowStartupSplitViewKey];
|
|
2618
|
+
[self sendWindowEventWithName:@"onWindowClosed"
|
|
2619
|
+
body:@{@"identifier": @"main", @"moduleName": @"main"}];
|
|
2620
|
+
resolve([self successJson]);
|
|
2621
|
+
});
|
|
2622
|
+
#else
|
|
2623
|
+
resolve([self failureJson:@"WindowManager is only available on macOS"]);
|
|
2624
|
+
#endif
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2627
|
+
- (void)showWindow:(NSString *)identifier resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
|
|
2628
|
+
{
|
|
2629
|
+
#if TARGET_OS_OSX
|
|
2630
|
+
RCTExecuteOnMainQueue(^{
|
|
2631
|
+
NSString *targetIdentifier = [self normalizeIdentifier:identifier];
|
|
2632
|
+
NSWindow *window = (NSWindow *)self.windows[targetIdentifier];
|
|
2633
|
+
if (!window) {
|
|
2634
|
+
reject(@"E_NOT_FOUND", @"Window not found", nil);
|
|
2635
|
+
return;
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2638
|
+
[NSApp activateIgnoringOtherApps:YES];
|
|
2639
|
+
[window makeKeyAndOrderFront:nil];
|
|
2640
|
+
resolve([self successJson]);
|
|
2641
|
+
});
|
|
2642
|
+
#else
|
|
2643
|
+
resolve([self failureJson:@"WindowManager is only available on macOS"]);
|
|
2644
|
+
#endif
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
- (void)setMainWindowOptions:(NSString *)optionsJson resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
|
|
2648
|
+
{
|
|
2649
|
+
#if TARGET_OS_OSX
|
|
2650
|
+
RCTExecuteOnMainQueue(^{
|
|
2651
|
+
NSWindow *mainWindow = [RNWindowManager getMainWindow];
|
|
2652
|
+
if (!mainWindow) {
|
|
2653
|
+
reject(@"E_NOT_FOUND", @"Main window not found", nil);
|
|
2654
|
+
return;
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
NSDictionary *options = [self parseObjectJSON:optionsJson];
|
|
2658
|
+
id startupSplitView = options[@"windowStyle"][@"startupSplitView"];
|
|
2659
|
+
if ([startupSplitView isKindOfClass:NSDictionary.class]) {
|
|
2660
|
+
// Persist window chrome only, just like AppKit's saved frame. No document data.
|
|
2661
|
+
if (![startupSplitView isEqual:[NSUserDefaults.standardUserDefaults dictionaryForKey:LegendMainWindowStartupSplitViewKey]]) {
|
|
2662
|
+
[NSUserDefaults.standardUserDefaults setObject:startupSplitView forKey:LegendMainWindowStartupSplitViewKey];
|
|
2663
|
+
}
|
|
2664
|
+
} else if (startupSplitView == NSNull.null) {
|
|
2665
|
+
[NSUserDefaults.standardUserDefaults removeObjectForKey:LegendMainWindowStartupSplitViewKey];
|
|
2666
|
+
}
|
|
2667
|
+
LegendApplyWindowOptions(mainWindow, options);
|
|
2668
|
+
[self applyTitlebarControlsFromOptions:options toWindow:mainWindow identifier:@"main"];
|
|
2669
|
+
[self applyToolbarItemsFromOptions:options toWindow:mainWindow identifier:@"main"];
|
|
2670
|
+
[self applyTitlebarMaterialFromOptions:options toWindow:mainWindow identifier:@"main"];
|
|
2671
|
+
resolve([self successJson]);
|
|
2672
|
+
});
|
|
2673
|
+
#else
|
|
2674
|
+
resolve([self failureJson:@"WindowManager is only available on macOS"]);
|
|
2675
|
+
#endif
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2678
|
+
- (void)setWindowOptions:(NSString *)identifier
|
|
2679
|
+
optionsJson:(NSString *)optionsJson
|
|
2680
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
2681
|
+
reject:(RCTPromiseRejectBlock)reject
|
|
2682
|
+
{
|
|
2683
|
+
#if TARGET_OS_OSX
|
|
2684
|
+
RCTExecuteOnMainQueue(^{
|
|
2685
|
+
NSString *targetIdentifier = [self normalizeIdentifier:identifier];
|
|
2686
|
+
NSDictionary *options = [self parseObjectJSON:optionsJson];
|
|
2687
|
+
NSWindow *window = (NSWindow *)self.windows[targetIdentifier];
|
|
2688
|
+
if (!window) {
|
|
2689
|
+
reject(@"E_NOT_FOUND", @"Window not found", nil);
|
|
2690
|
+
return;
|
|
2691
|
+
}
|
|
2692
|
+
[self recordWindowOptions:options
|
|
2693
|
+
identifier:targetIdentifier
|
|
2694
|
+
moduleName:self.moduleNames[targetIdentifier] ?: @""];
|
|
2695
|
+
LegendApplyWindowOptions(window, options);
|
|
2696
|
+
[self applyTitlebarControlsFromOptions:options toWindow:window identifier:targetIdentifier];
|
|
2697
|
+
[self applyToolbarItemsFromOptions:options toWindow:window identifier:targetIdentifier];
|
|
2698
|
+
[self applyTitlebarMaterialFromOptions:options toWindow:window identifier:targetIdentifier];
|
|
2699
|
+
RCTUIView *rootView = self.rootViews[targetIdentifier];
|
|
2700
|
+
LegendSizeRootViewToWindow(rootView, window);
|
|
2701
|
+
resolve([self successJson]);
|
|
2702
|
+
});
|
|
2703
|
+
#else
|
|
2704
|
+
resolve([self failureJson:@"WindowManager is only available on macOS"]);
|
|
2705
|
+
#endif
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
- (void)getMainWindowFrame:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
|
|
2709
|
+
{
|
|
2710
|
+
#if TARGET_OS_OSX
|
|
2711
|
+
RCTExecuteOnMainQueue(^{
|
|
2712
|
+
NSWindow *mainWindow = [RNWindowManager getMainWindow];
|
|
2713
|
+
if (!mainWindow) {
|
|
2714
|
+
reject(@"no_main_window", @"Main window not found", nil);
|
|
2715
|
+
return;
|
|
2716
|
+
}
|
|
2717
|
+
resolve([self jsonStringFromObject:[self frameDictionary:mainWindow.frame]]);
|
|
2718
|
+
});
|
|
2719
|
+
#else
|
|
2720
|
+
resolve([self jsonStringFromObject:@{@"x": @0, @"y": @0, @"width": @0, @"height": @0}]);
|
|
2721
|
+
#endif
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
- (void)setMainWindowFrame:(NSString *)frameJson resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
|
|
2725
|
+
{
|
|
2726
|
+
#if TARGET_OS_OSX
|
|
2727
|
+
RCTExecuteOnMainQueue(^{
|
|
2728
|
+
NSWindow *mainWindow = [RNWindowManager getMainWindow];
|
|
2729
|
+
if (!mainWindow) {
|
|
2730
|
+
reject(@"no_main_window", @"Main window not found", nil);
|
|
2731
|
+
return;
|
|
2732
|
+
}
|
|
2733
|
+
NSDictionary *frameDict = [self parseObjectJSON:frameJson];
|
|
2734
|
+
NSRect newFrame = NSMakeRect([frameDict[@"x"] doubleValue],
|
|
2735
|
+
[frameDict[@"y"] doubleValue],
|
|
2736
|
+
[frameDict[@"width"] doubleValue],
|
|
2737
|
+
[frameDict[@"height"] doubleValue]);
|
|
2738
|
+
[mainWindow setFrame:newFrame display:YES animate:NO];
|
|
2739
|
+
resolve([self successJson]);
|
|
2740
|
+
});
|
|
2741
|
+
#else
|
|
2742
|
+
resolve([self failureJson:@"WindowManager is only available on macOS"]);
|
|
2743
|
+
#endif
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2746
|
+
- (void)setWindowBlur:(NSString *)identifier
|
|
2747
|
+
radius:(double)radius
|
|
2748
|
+
durationMs:(double)durationMs
|
|
2749
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
2750
|
+
reject:(RCTPromiseRejectBlock)reject
|
|
2751
|
+
{
|
|
2752
|
+
#if TARGET_OS_OSX
|
|
2753
|
+
RCTExecuteOnMainQueue(^{
|
|
2754
|
+
NSString *targetIdentifier = [self normalizeIdentifier:identifier];
|
|
2755
|
+
NSWindow *window = (NSWindow *)self.windows[targetIdentifier];
|
|
2756
|
+
if (!window) {
|
|
2757
|
+
reject(@"E_NOT_FOUND", @"Target window not found for blur animation", nil);
|
|
2758
|
+
return;
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
RCTUIView *rootView = self.rootViews[targetIdentifier];
|
|
2762
|
+
NSView *contentView = window.contentView ?: rootView;
|
|
2763
|
+
if (!contentView) {
|
|
2764
|
+
reject(@"no_content_view", @"Window does not have a content view to blur", nil);
|
|
2765
|
+
return;
|
|
2766
|
+
}
|
|
2767
|
+
|
|
2768
|
+
contentView.wantsLayer = YES;
|
|
2769
|
+
contentView.layerUsesCoreImageFilters = YES;
|
|
2770
|
+
if (!contentView.layer) {
|
|
2771
|
+
contentView.layer = [CALayer layer];
|
|
2772
|
+
}
|
|
2773
|
+
contentView.layer.masksToBounds = NO;
|
|
2774
|
+
|
|
2775
|
+
CIFilter *blurFilter = self.windowBlurFilters[targetIdentifier];
|
|
2776
|
+
if (!blurFilter) {
|
|
2777
|
+
blurFilter = [CIFilter filterWithName:@"CIGaussianBlur"];
|
|
2778
|
+
if (!blurFilter) {
|
|
2779
|
+
reject(@"filter_unavailable", @"CIGaussianBlur filter could not be created", nil);
|
|
2780
|
+
return;
|
|
2781
|
+
}
|
|
2782
|
+
blurFilter.name = @"legendOverlayBlur";
|
|
2783
|
+
[blurFilter setDefaults];
|
|
2784
|
+
[blurFilter setValue:@0 forKey:kCIInputRadiusKey];
|
|
2785
|
+
self.windowBlurFilters[targetIdentifier] = blurFilter;
|
|
2786
|
+
}
|
|
2787
|
+
|
|
2788
|
+
BOOL filterAttached = NO;
|
|
2789
|
+
for (id existingFilter in contentView.layer.filters ?: @[]) {
|
|
2790
|
+
if ([existingFilter isKindOfClass:CIFilter.class] && [[existingFilter name] isEqualToString:@"legendOverlayBlur"]) {
|
|
2791
|
+
filterAttached = YES;
|
|
2792
|
+
break;
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
if (!filterAttached) {
|
|
2796
|
+
NSMutableArray *filters = [NSMutableArray arrayWithArray:contentView.layer.filters ?: @[]];
|
|
2797
|
+
[filters addObject:blurFilter];
|
|
2798
|
+
contentView.layer.filters = filters;
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2801
|
+
CGFloat targetRadius = radius;
|
|
2802
|
+
CGFloat currentRadius = [[blurFilter valueForKey:kCIInputRadiusKey] doubleValue];
|
|
2803
|
+
NSTimeInterval durationSeconds = durationMs / 1000.0;
|
|
2804
|
+
if (durationSeconds <= 0) {
|
|
2805
|
+
[CATransaction begin];
|
|
2806
|
+
[CATransaction setDisableActions:YES];
|
|
2807
|
+
[blurFilter setValue:@(targetRadius) forKey:kCIInputRadiusKey];
|
|
2808
|
+
[CATransaction commit];
|
|
2809
|
+
[contentView.layer removeAnimationForKey:@"legendOverlayBlurAnimation"];
|
|
2810
|
+
resolve([self successJson]);
|
|
2811
|
+
return;
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
[contentView.layer removeAnimationForKey:@"legendOverlayBlurAnimation"];
|
|
2815
|
+
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"filters.legendOverlayBlur.inputRadius"];
|
|
2816
|
+
animation.fromValue = @(currentRadius);
|
|
2817
|
+
animation.toValue = @(targetRadius);
|
|
2818
|
+
animation.duration = durationSeconds;
|
|
2819
|
+
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
|
|
2820
|
+
animation.fillMode = kCAFillModeForwards;
|
|
2821
|
+
animation.removedOnCompletion = NO;
|
|
2822
|
+
|
|
2823
|
+
[CATransaction begin];
|
|
2824
|
+
[CATransaction setCompletionBlock:^{
|
|
2825
|
+
[CATransaction begin];
|
|
2826
|
+
[CATransaction setDisableActions:YES];
|
|
2827
|
+
[blurFilter setValue:@(targetRadius) forKey:kCIInputRadiusKey];
|
|
2828
|
+
[CATransaction commit];
|
|
2829
|
+
resolve([self successJson]);
|
|
2830
|
+
}];
|
|
2831
|
+
[contentView.layer addAnimation:animation forKey:@"legendOverlayBlurAnimation"];
|
|
2832
|
+
[CATransaction commit];
|
|
2833
|
+
});
|
|
2834
|
+
#else
|
|
2835
|
+
resolve([self failureJson:@"WindowManager is only available on macOS"]);
|
|
2836
|
+
#endif
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2839
|
+
#if TARGET_OS_OSX
|
|
2840
|
+
+ (NSWindow *)getMainWindow
|
|
2841
|
+
{
|
|
2842
|
+
// AppKit's window list includes precreated/restored document windows and has
|
|
2843
|
+
// no stable ordering. Both supported hosts own the main window on the delegate.
|
|
2844
|
+
id<RNWindowManagerRootViewFactoryProvider> delegate = (id)NSApplication.sharedApplication.delegate;
|
|
2845
|
+
return [delegate respondsToSelector:@selector(window)] ? delegate.window : nil;
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2848
|
+
- (void)setupMainWindowObservers
|
|
2849
|
+
{
|
|
2850
|
+
if (self.mainWindowObserversInstalled) {
|
|
2851
|
+
return;
|
|
2852
|
+
}
|
|
2853
|
+
NSWindow *mainWindow = [RNWindowManager getMainWindow];
|
|
2854
|
+
if (!mainWindow) {
|
|
2855
|
+
return;
|
|
2856
|
+
}
|
|
2857
|
+
self.mainWindowObserversInstalled = YES;
|
|
2858
|
+
NSNotificationCenter *center = NSNotificationCenter.defaultCenter;
|
|
2859
|
+
[center addObserver:self selector:@selector(mainWindowDidMove:) name:NSWindowDidMoveNotification object:mainWindow];
|
|
2860
|
+
[center addObserver:self selector:@selector(mainWindowDidResize:) name:NSWindowDidResizeNotification object:mainWindow];
|
|
2861
|
+
[center addObserver:self selector:@selector(mainWindowDidBecomeKey:) name:NSWindowDidBecomeKeyNotification object:mainWindow];
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2864
|
+
- (void)mainWindowDidMove:(NSNotification *)notification
|
|
2865
|
+
{
|
|
2866
|
+
NSWindow *window = notification.object;
|
|
2867
|
+
[self sendWindowEventWithName:@"onMainWindowMoved" body:[self frameDictionary:window.frame]];
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2870
|
+
- (void)mainWindowDidResize:(NSNotification *)notification
|
|
2871
|
+
{
|
|
2872
|
+
NSWindow *window = notification.object;
|
|
2873
|
+
RCTUIView *rootView = LegendManagedRootView(window);
|
|
2874
|
+
if (rootView) {
|
|
2875
|
+
LegendSizeRootViewToWindow(rootView, window);
|
|
2876
|
+
}
|
|
2877
|
+
[self sendWindowEventWithName:@"onMainWindowResized" body:[self frameDictionary:window.frame]];
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2880
|
+
- (void)mainWindowDidBecomeKey:(NSNotification *)notification
|
|
2881
|
+
{
|
|
2882
|
+
[self sendWindowEventWithName:@"onWindowFocused" body:@{@"identifier": @"main", @"moduleName": @"main"}];
|
|
2883
|
+
}
|
|
2884
|
+
|
|
2885
|
+
- (void)windowDidBecomeKey:(NSNotification *)notification
|
|
2886
|
+
{
|
|
2887
|
+
NSString *identifier = [self identifierForWindow:notification.object];
|
|
2888
|
+
if (!identifier) {
|
|
2889
|
+
return;
|
|
2890
|
+
}
|
|
2891
|
+
[self sendWindowEventWithName:@"onWindowFocused"
|
|
2892
|
+
body:@{@"identifier": identifier, @"moduleName": self.moduleNames[identifier] ?: @""}];
|
|
2893
|
+
}
|
|
2894
|
+
|
|
2895
|
+
- (void)windowDidMove:(NSNotification *)notification
|
|
2896
|
+
{
|
|
2897
|
+
NSWindow *window = notification.object;
|
|
2898
|
+
NSString *identifier = [self identifierForWindow:window];
|
|
2899
|
+
if (!identifier) {
|
|
2900
|
+
return;
|
|
2901
|
+
}
|
|
2902
|
+
[self sendWindowEventWithName:@"onWindowMoved"
|
|
2903
|
+
body:@{
|
|
2904
|
+
@"identifier": identifier,
|
|
2905
|
+
@"moduleName": self.moduleNames[identifier] ?: @"",
|
|
2906
|
+
@"frame": [self frameDictionary:window.frame],
|
|
2907
|
+
}];
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2910
|
+
- (void)windowDidResize:(NSNotification *)notification
|
|
2911
|
+
{
|
|
2912
|
+
NSWindow *window = notification.object;
|
|
2913
|
+
NSString *identifier = [self identifierForWindow:window];
|
|
2914
|
+
if (!identifier) {
|
|
2915
|
+
return;
|
|
2916
|
+
}
|
|
2917
|
+
RCTUIView *rootView = LegendManagedRootView(window);
|
|
2918
|
+
if (rootView) {
|
|
2919
|
+
LegendSizeRootViewToWindow(rootView, window);
|
|
2920
|
+
}
|
|
2921
|
+
[self sendWindowEventWithName:@"onWindowResized"
|
|
2922
|
+
body:@{
|
|
2923
|
+
@"identifier": identifier,
|
|
2924
|
+
@"moduleName": self.moduleNames[identifier] ?: @"",
|
|
2925
|
+
@"frame": [self frameDictionary:window.frame],
|
|
2926
|
+
}];
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2929
|
+
- (void)windowWillClose:(NSNotification *)notification
|
|
2930
|
+
{
|
|
2931
|
+
NSString *identifier = [self identifierForWindow:notification.object];
|
|
2932
|
+
if (identifier) {
|
|
2933
|
+
[self handleWindowClosedForIdentifier:identifier closeWindow:NO];
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
|
|
2937
|
+
- (void)sendWindowEventWithName:(NSString *)eventName body:(id)body
|
|
2938
|
+
{
|
|
2939
|
+
#if __has_include(<RNDesktopApp/SparkDesktop.h>)
|
|
2940
|
+
NSString *type = nil;
|
|
2941
|
+
if ([eventName isEqual:@"onTitlebarControlPressed"]) type = @"titleBarAction";
|
|
2942
|
+
else if ([eventName isEqual:@"onToolbarItemSelected"]) type = body[@"action"] ? @"toolbarMenuAction" : [body[@"kind"] isEqual:@"selection"] ? @"toolbarSelectionChanged" : @"toolbarAction";
|
|
2943
|
+
else if ([eventName isEqual:@"onToolbarSearch"]) type = [body[@"submitted"] boolValue] ? @"toolbarSearchSubmitted" : @"toolbarSearchChanged";
|
|
2944
|
+
if (type) {
|
|
2945
|
+
NSMutableDictionary *event = [body mutableCopy]; event[@"type"] = type; event[@"windowId"] = body[@"identifier"] ?: @"";
|
|
2946
|
+
SparkEmit(event); return;
|
|
2947
|
+
}
|
|
2948
|
+
#endif
|
|
2949
|
+
|
|
2950
|
+
if (!self.hasListeners) {
|
|
2951
|
+
return;
|
|
2952
|
+
}
|
|
2953
|
+
[self sendEventWithName:eventName body:body];
|
|
2954
|
+
}
|
|
2955
|
+
|
|
2956
|
+
#if __has_include(<RNDesktopApp/SparkDesktop.h>)
|
|
2957
|
+
- (void)sparkDesktopEvent:(NSNotification *)notification
|
|
2958
|
+
{
|
|
2959
|
+
NSDictionary *event = notification.userInfo;
|
|
2960
|
+
if ([event[@"type"] isEqual:@"reopen"]) {
|
|
2961
|
+
// A visible host can have been hidden while other managed windows remained.
|
|
2962
|
+
BOOL mainHidden = [SparkLifecycleConfiguration()[@"mainWindow"][@"hidden"] boolValue] || [SparkLifecycleConfiguration()[@"mainWindow"][@"reopenBehavior"] isEqual:@"visibleWindows"];
|
|
2963
|
+
NSNumber *visible = mainHidden ? event[@"hasVisibleWindows"] : event[@"mainWindowWasVisible"];
|
|
2964
|
+
[self sendWindowEventWithName:@"onApplicationReopenRequested" body:@{@"hasVisibleWindows": visible ?: @NO}];
|
|
2965
|
+
} else if ([event[@"type"] isEqual:@"closeRequested"] && [event[@"windowId"] isEqual:@"main"]) {
|
|
2966
|
+
[self mainWindowCloseRequested:notification];
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
#endif
|
|
2970
|
+
|
|
2971
|
+
- (void)applicationReopenRequested:(NSNotification *)notification
|
|
2972
|
+
{
|
|
2973
|
+
NSNumber *hasVisibleWindows = notification.userInfo[@"hasVisibleWindows"];
|
|
2974
|
+
[self sendWindowEventWithName:@"onApplicationReopenRequested"
|
|
2975
|
+
body:@{@"hasVisibleWindows": hasVisibleWindows ?: @NO}];
|
|
2976
|
+
}
|
|
2977
|
+
|
|
2978
|
+
- (void)applicationWillTerminate:(NSNotification *)notification
|
|
2979
|
+
{
|
|
2980
|
+
LegendApplicationIsTerminating = YES;
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2983
|
+
- (void)mainWindowCloseRequested:(NSNotification *)notification
|
|
2984
|
+
{
|
|
2985
|
+
[self sendWindowEventWithName:@"onWindowCloseRequested"
|
|
2986
|
+
body:@{@"identifier": @"main", @"moduleName": @"main"}];
|
|
2987
|
+
}
|
|
2988
|
+
|
|
2989
|
+
- (void)screensChanged:(NSNotification *)notification
|
|
2990
|
+
{
|
|
2991
|
+
[self sendWindowEventWithName:@"onDisplaysChanged" body:[self displayDictionaries]];
|
|
2992
|
+
}
|
|
2993
|
+
|
|
2994
|
+
- (BOOL)windowShouldClose:(NSWindow *)window
|
|
2995
|
+
{
|
|
2996
|
+
NSString *identifier = [self identifierForWindow:window];
|
|
2997
|
+
if (!identifier) {
|
|
2998
|
+
return NO;
|
|
2999
|
+
}
|
|
3000
|
+
|
|
3001
|
+
if ([self.closeRequestIdentifiers containsObject:identifier]) {
|
|
3002
|
+
[self sendWindowEventWithName:@"onWindowCloseRequested"
|
|
3003
|
+
body:@{@"identifier": identifier, @"moduleName": self.moduleNames[identifier] ?: @""}];
|
|
3004
|
+
return NO;
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
return YES;
|
|
3008
|
+
}
|
|
3009
|
+
|
|
3010
|
+
- (NSDictionary *)frameDictionary:(NSRect)frame
|
|
3011
|
+
{
|
|
3012
|
+
return @{
|
|
3013
|
+
@"x": @(frame.origin.x),
|
|
3014
|
+
@"y": @(frame.origin.y),
|
|
3015
|
+
@"width": @(frame.size.width),
|
|
3016
|
+
@"height": @(frame.size.height),
|
|
3017
|
+
};
|
|
3018
|
+
}
|
|
3019
|
+
|
|
3020
|
+
- (NSString *)identifierForWindow:(NSWindow *)window
|
|
3021
|
+
{
|
|
3022
|
+
if (!window) {
|
|
3023
|
+
return nil;
|
|
3024
|
+
}
|
|
3025
|
+
for (NSString *identifier in self.windows.allKeys) {
|
|
3026
|
+
if ((NSWindow *)self.windows[identifier] == window) {
|
|
3027
|
+
return identifier;
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
return nil;
|
|
3031
|
+
}
|
|
3032
|
+
|
|
3033
|
+
- (void)handleWindowClosedForIdentifier:(NSString *)identifier closeWindow:(BOOL)shouldCloseWindow
|
|
3034
|
+
{
|
|
3035
|
+
NSString *moduleName = self.moduleNames[identifier] ?: @"";
|
|
3036
|
+
NSWindow *window = (NSWindow *)self.windows[identifier];
|
|
3037
|
+
RCTUIView *rootView = self.rootViews[identifier];
|
|
3038
|
+
CIFilter *blurFilter = self.windowBlurFilters[identifier];
|
|
3039
|
+
if (blurFilter) {
|
|
3040
|
+
NSView *contentView = window.contentView ?: rootView;
|
|
3041
|
+
if (contentView.layer) {
|
|
3042
|
+
[contentView.layer removeAnimationForKey:@"legendOverlayBlurAnimation"];
|
|
3043
|
+
NSMutableArray *remainingFilters = [NSMutableArray array];
|
|
3044
|
+
for (id filter in contentView.layer.filters ?: @[]) {
|
|
3045
|
+
if ([filter isKindOfClass:CIFilter.class] && [[filter name] isEqualToString:@"legendOverlayBlur"]) {
|
|
3046
|
+
continue;
|
|
3047
|
+
}
|
|
3048
|
+
[remainingFilters addObject:filter];
|
|
3049
|
+
}
|
|
3050
|
+
contentView.layer.filters = remainingFilters;
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
[self.windowBlurFilters removeObjectForKey:identifier];
|
|
3054
|
+
if (window) {
|
|
3055
|
+
[self removeTitlebarMaterialForIdentifier:identifier];
|
|
3056
|
+
[self removeTitlebarControlsForIdentifier:identifier fromWindow:window];
|
|
3057
|
+
NSToolbar *toolbar = window.toolbar;
|
|
3058
|
+
if (toolbar) {
|
|
3059
|
+
if (toolbar.identifier) {
|
|
3060
|
+
[self.toolbarItemConfigs removeObjectForKey:toolbar.identifier];
|
|
3061
|
+
}
|
|
3062
|
+
toolbar.delegate = nil;
|
|
3063
|
+
while (toolbar.items.count > 0) {
|
|
3064
|
+
[toolbar removeItemAtIndex:toolbar.items.count - 1];
|
|
3065
|
+
}
|
|
3066
|
+
window.toolbar = nil;
|
|
3067
|
+
}
|
|
3068
|
+
window.delegate = nil;
|
|
3069
|
+
if (rootView) {
|
|
3070
|
+
[rootView removeFromSuperview];
|
|
3071
|
+
objc_setAssociatedObject(window, &LegendManagedRootViewKey, nil, OBJC_ASSOCIATION_ASSIGN);
|
|
3072
|
+
}
|
|
3073
|
+
NSView *emptyContentView = [[NSView alloc] initWithFrame:window.contentView ? window.contentView.frame : NSZeroRect];
|
|
3074
|
+
emptyContentView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
|
3075
|
+
window.contentView = emptyContentView;
|
|
3076
|
+
} else {
|
|
3077
|
+
[self.titlebarMaterialViews removeObjectForKey:identifier];
|
|
3078
|
+
[self.titlebarAccessoryControllers removeObjectForKey:identifier];
|
|
3079
|
+
}
|
|
3080
|
+
[self.closeRequestIdentifiers removeObject:identifier];
|
|
3081
|
+
[self.toolbarItemConfigs removeObjectForKey:identifier];
|
|
3082
|
+
NSDictionary *windowOptions = self.windowOptions[identifier];
|
|
3083
|
+
if (!LegendApplicationIsTerminating && [windowOptions[@"restoreOnLaunch"] boolValue]) {
|
|
3084
|
+
LegendSetRestorableWindowOptions(identifier, nil);
|
|
3085
|
+
}
|
|
3086
|
+
[self.windows removeObjectForKey:identifier];
|
|
3087
|
+
[self.rootViews removeObjectForKey:identifier];
|
|
3088
|
+
[self.moduleNames removeObjectForKey:identifier];
|
|
3089
|
+
[self.windowOptions removeObjectForKey:identifier];
|
|
3090
|
+
if (shouldCloseWindow && window) {
|
|
3091
|
+
[window close];
|
|
3092
|
+
}
|
|
3093
|
+
[self sendWindowEventWithName:@"onWindowClosed" body:@{@"identifier": identifier ?: @"", @"moduleName": moduleName ?: @""}];
|
|
3094
|
+
}
|
|
3095
|
+
|
|
3096
|
+
- (void)recordWindowOptions:(NSDictionary *)options identifier:(NSString *)identifier moduleName:(NSString *)moduleName
|
|
3097
|
+
{
|
|
3098
|
+
if (identifier.length == 0 || ![options isKindOfClass:NSDictionary.class]) {
|
|
3099
|
+
return;
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3102
|
+
NSDictionary *persistedOptions = LegendFindRestorableWindowOptions(identifier);
|
|
3103
|
+
NSDictionary *previousOptions = self.windowOptions[identifier] ?: persistedOptions ?: @{};
|
|
3104
|
+
NSMutableDictionary *mergedOptions = [previousOptions mutableCopy];
|
|
3105
|
+
[mergedOptions addEntriesFromDictionary:options];
|
|
3106
|
+
NSDictionary *previousWindowStyle = [previousOptions[@"windowStyle"] isKindOfClass:NSDictionary.class]
|
|
3107
|
+
? previousOptions[@"windowStyle"]
|
|
3108
|
+
: @{};
|
|
3109
|
+
NSDictionary *nextWindowStyle = [options[@"windowStyle"] isKindOfClass:NSDictionary.class]
|
|
3110
|
+
? options[@"windowStyle"]
|
|
3111
|
+
: nil;
|
|
3112
|
+
if (nextWindowStyle) {
|
|
3113
|
+
NSMutableDictionary *mergedWindowStyle = [previousWindowStyle mutableCopy];
|
|
3114
|
+
[mergedWindowStyle addEntriesFromDictionary:nextWindowStyle];
|
|
3115
|
+
mergedOptions[@"windowStyle"] = mergedWindowStyle;
|
|
3116
|
+
}
|
|
3117
|
+
mergedOptions[@"identifier"] = identifier;
|
|
3118
|
+
if (moduleName.length > 0) {
|
|
3119
|
+
mergedOptions[@"moduleName"] = moduleName;
|
|
3120
|
+
}
|
|
3121
|
+
self.windowOptions[identifier] = mergedOptions;
|
|
3122
|
+
|
|
3123
|
+
if (LegendDictionaryHasKey(options, @"restoreOnLaunch")) {
|
|
3124
|
+
if ([options[@"restoreOnLaunch"] boolValue]) {
|
|
3125
|
+
LegendSetRestorableWindowOptions(identifier, mergedOptions);
|
|
3126
|
+
} else {
|
|
3127
|
+
LegendSetRestorableWindowOptions(identifier, nil);
|
|
3128
|
+
LegendRemovePrecreatedWindow(identifier);
|
|
3129
|
+
}
|
|
3130
|
+
} else if ([mergedOptions[@"restoreOnLaunch"] boolValue]) {
|
|
3131
|
+
LegendSetRestorableWindowOptions(identifier, mergedOptions);
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
|
|
3135
|
+
- (NSDictionary *)initialPropsFromOptions:(NSDictionary *)options
|
|
3136
|
+
{
|
|
3137
|
+
id initialPropsCandidate = options[@"initialProperties"];
|
|
3138
|
+
return [initialPropsCandidate isKindOfClass:NSDictionary.class] ? initialPropsCandidate : nil;
|
|
3139
|
+
}
|
|
3140
|
+
|
|
3141
|
+
- (NSString *)normalizeIdentifier:(NSString *)identifier
|
|
3142
|
+
{
|
|
3143
|
+
return [identifier isKindOfClass:NSString.class] && identifier.length > 0 ? identifier : @"default";
|
|
3144
|
+
}
|
|
3145
|
+
#endif
|
|
3146
|
+
|
|
3147
|
+
- (void)invalidate
|
|
3148
|
+
{
|
|
3149
|
+
// React's host restarts the existing surfaces on reload. Keep their native
|
|
3150
|
+
// registry, but retire this runtime's observers and event delivery.
|
|
3151
|
+
self.hasListeners = NO;
|
|
3152
|
+
#if TARGET_OS_OSX
|
|
3153
|
+
[NSNotificationCenter.defaultCenter removeObserver:self];
|
|
3154
|
+
#endif
|
|
3155
|
+
[super invalidate];
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3158
|
+
- (void)dealloc
|
|
3159
|
+
{
|
|
3160
|
+
#if TARGET_OS_OSX
|
|
3161
|
+
if (self.displaySleepActivity) {
|
|
3162
|
+
[NSProcessInfo.processInfo endActivity:self.displaySleepActivity];
|
|
3163
|
+
self.displaySleepActivity = nil;
|
|
3164
|
+
}
|
|
3165
|
+
[NSNotificationCenter.defaultCenter removeObserver:self];
|
|
3166
|
+
#endif
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
@end
|