pythonnative 0.40.0__cp313-cp313-win_amd64.whl
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.
- pythonnative/__init__.py +411 -0
- pythonnative/_ios_log.py +92 -0
- pythonnative/_yoga.cp313-win_amd64.pyd +0 -0
- pythonnative/alerts.py +236 -0
- pythonnative/animated.py +1687 -0
- pythonnative/animation_graph.py +78 -0
- pythonnative/appearance.py +124 -0
- pythonnative/bootstrap.py +100 -0
- pythonnative/bridge/__init__.py +302 -0
- pythonnative/bridge/android.py +78 -0
- pythonnative/bridge/codec.py +170 -0
- pythonnative/bridge/commits.py +114 -0
- pythonnative/bridge/fake.py +243 -0
- pythonnative/bridge/ios.py +138 -0
- pythonnative/bridge/web.py +489 -0
- pythonnative/cli/__init__.py +7 -0
- pythonnative/cli/pn.py +1214 -0
- pythonnative/component.py +271 -0
- pythonnative/components/__init__.py +111 -0
- pythonnative/components/_base.py +166 -0
- pythonnative/components/controls.py +521 -0
- pythonnative/components/layout.py +518 -0
- pythonnative/components/lists.py +785 -0
- pythonnative/components/media.py +204 -0
- pythonnative/components/overlays.py +107 -0
- pythonnative/components/pressable.py +252 -0
- pythonnative/components/structural.py +167 -0
- pythonnative/components/text.py +291 -0
- pythonnative/devclient.py +824 -0
- pythonnative/devserver/__init__.py +34 -0
- pythonnative/devserver/server.py +718 -0
- pythonnative/devserver/static/animation_graph.js +82 -0
- pythonnative/devserver/static/bridge.js +147 -0
- pythonnative/devserver/static/colors.js +70 -0
- pythonnative/devserver/static/host.js +552 -0
- pythonnative/devserver/static/index.html +60 -0
- pythonnative/devserver/static/layout.js +85 -0
- pythonnative/devserver/static/preview.css +415 -0
- pythonnative/devserver/static/renderer.js +1994 -0
- pythonnative/devserver/static/shell.js +345 -0
- pythonnative/devserver/static/yoga/LICENSE +21 -0
- pythonnative/devserver/static/yoga/binaries/yoga-wasm-base64-esm.js +75 -0
- pythonnative/devserver/static/yoga/src/generated/YGEnums.d.ts +189 -0
- pythonnative/devserver/static/yoga/src/generated/YGEnums.js +211 -0
- pythonnative/devserver/static/yoga/src/generated/YGEnums.js.map +1 -0
- pythonnative/devserver/static/yoga/src/index.d.ts +12 -0
- pythonnative/devserver/static/yoga/src/index.js +16 -0
- pythonnative/devserver/static/yoga/src/index.js.map +1 -0
- pythonnative/devserver/static/yoga/src/load.d.ts +11 -0
- pythonnative/devserver/static/yoga/src/load.js +17 -0
- pythonnative/devserver/static/yoga/src/load.js.map +1 -0
- pythonnative/devserver/static/yoga/src/wrapAssembly.d.ts +155 -0
- pythonnative/devserver/static/yoga/src/wrapAssembly.js +125 -0
- pythonnative/devserver/static/yoga/src/wrapAssembly.js.map +1 -0
- pythonnative/devserver/watcher.py +267 -0
- pythonnative/devserver/ws.py +421 -0
- pythonnative/diagnostics.py +241 -0
- pythonnative/element.py +159 -0
- pythonnative/equality.py +19 -0
- pythonnative/events.py +231 -0
- pythonnative/gestures.py +1333 -0
- pythonnative/hooks.py +1621 -0
- pythonnative/hosts/__init__.py +63 -0
- pythonnative/hosts/base.py +596 -0
- pythonnative/hosts/native.py +302 -0
- pythonnative/hot_reload.py +585 -0
- pythonnative/layout.py +328 -0
- pythonnative/mutations.py +142 -0
- pythonnative/native/android/build.gradle +52 -0
- pythonnative/native/android/gradle.properties +3 -0
- pythonnative/native/android/settings.gradle +12 -0
- pythonnative/native/android/src/main/AndroidManifest.xml +11 -0
- pythonnative/native/android/src/main/cpp/CMakeLists.txt +9 -0
- pythonnative/native/android/src/main/cpp/yoga_jni.cpp +52 -0
- pythonnative/native/android/src/main/java/com/pythonnative/generated/NativeModules.kt +5 -0
- pythonnative/native/android/src/main/java/com/pythonnative/generated/NativeProps.kt +3705 -0
- pythonnative/native/android/src/main/java/com/pythonnative/generated/PNContracts.kt +129 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/PNBridge.kt +261 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/PythonHost.kt +20 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/animation/AnimationGraph.kt +108 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/animation/AnimationSpecs.kt +141 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/animation/PNAnimator.kt +257 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/CommitState.kt +106 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/JsonUtil.kt +175 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/MainThread.kt +40 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/PNLog.kt +33 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/PNRegistry.kt +82 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/PNTransaction.kt +80 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/TransactionApplier.kt +117 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/ViewRegistry.kt +74 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/BuiltinComponents.kt +37 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ButtonManager.kt +32 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ComponentManager.kt +213 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ContainerManagers.kt +69 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ControlManagers.kt +186 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/DatePickerManager.kt +123 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ImageLoader.kt +141 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ImageManager.kt +162 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ModalManager.kt +134 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/PNColor.kt +201 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/PickerManager.kt +86 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/PortalManager.kt +55 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/PressableManager.kt +122 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ScreenStackManager.kt +110 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ScrollViewManager.kt +214 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/SegmentedControlManager.kt +113 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/StatusBarManager.kt +52 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/TabBarManager.kt +131 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/TextInputManager.kt +250 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/TextManager.kt +233 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ViewStyler.kt +355 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/VirtualListManager.kt +159 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/WebViewManager.kt +149 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/gestures/GestureArbiter.kt +233 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/gestures/GestureCoordinator.kt +127 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/gestures/GestureRecognizers.kt +527 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/layout/NativeLayout.kt +145 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/layout/YogaNode.kt +29 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/BuiltinModules.kt +73 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/DeviceModules.kt +316 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/HostModule.kt +61 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/MediaModules.kt +200 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/NativeModule.kt +158 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/NotificationsModule.kt +100 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/PermissionsModule.kt +99 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/StorageModules.kt +89 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/SystemModules.kt +178 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/plugins/GeneratedPlugins.kt +16 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/screens/PNScreenFragment.kt +188 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/screens/ScreenRegistry.kt +80 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/views/PNAccessibilityDelegate.kt +48 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/views/PNBorderDrawable.kt +76 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/views/PNEditText.kt +18 -0
- pythonnative/native/android/src/main/java/com/pythonnative/runtime/views/PNFrameLayout.kt +49 -0
- pythonnative/native/android/src/test/java/com/pythonnative/runtime/AnimationSpecsTest.kt +75 -0
- pythonnative/native/android/src/test/java/com/pythonnative/runtime/GestureArbiterTest.kt +197 -0
- pythonnative/native/android/src/test/java/com/pythonnative/runtime/PNColorTest.kt +47 -0
- pythonnative/native/android/src/test/java/com/pythonnative/runtime/PNTransactionTest.kt +78 -0
- pythonnative/native/android/src/test/java/com/pythonnative/runtime/PromiseTest.kt +89 -0
- pythonnative/native/ios/Package.swift +23 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Animation/PNAnimationGraph.swift +199 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Animation/PNAnimator.swift +312 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNBridge.swift +255 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNCommit.swift +104 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNRegistry.swift +142 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNTransaction.swift +166 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNViewRegistry.swift +53 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNButtonManagers.swift +207 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNColor.swift +173 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNComponentManager.swift +183 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNContainerView.swift +56 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNControlManagers.swift +281 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNImageManager.swift +222 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNOverlayManagers.swift +243 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNPressableManager.swift +114 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNScreenStackManager.swift +107 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNScrollViewManager.swift +197 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNTabBarManager.swift +106 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNTextInputManager.swift +297 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNTextManager.swift +230 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNTransform.swift +82 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNViewManager.swift +86 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNViewState.swift +87 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNViewStyler.swift +340 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNVirtualListManager.swift +155 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Components/PNWebViewManager.swift +124 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Generated/NativeModules.swift +1 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Generated/NativeProps.swift +3775 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Generated/PNContracts.swift +46 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Gestures/PNGestureCoordinator.swift +260 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Layout/PNLayout.swift +173 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Modules/AlertModule.swift +92 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Modules/HostModule.swift +80 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Modules/LifecycleModules.swift +174 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Modules/MediaModules.swift +177 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Modules/PNNativeModule.swift +168 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Modules/PermissionModules.swift +266 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Modules/SystemModules.swift +251 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Plugins/PNPluginRegistration.swift +8 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Screens/PNScreenRegistry.swift +46 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Screens/PNViewController.swift +237 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Support/PNCompat.swift +46 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Support/PNJSON.swift +173 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Support/PNLog.swift +47 -0
- pythonnative/native/ios/Sources/PythonNativeKit/Support/PNWindow.swift +51 -0
- pythonnative/native/ios/Tests/PythonNativeKitTests/PNAnimationGraphTests.swift +26 -0
- pythonnative/native/ios/Tests/PythonNativeKitTests/PNColorTests.swift +50 -0
- pythonnative/native/ios/Tests/PythonNativeKitTests/PNCommitTests.swift +86 -0
- pythonnative/native/ios/Tests/PythonNativeKitTests/PNGestureTests.swift +101 -0
- pythonnative/native/ios/Tests/PythonNativeKitTests/PNManagerTests.swift +126 -0
- pythonnative/native/ios/Tests/PythonNativeKitTests/PNModuleTests.swift +111 -0
- pythonnative/native/ios/Tests/PythonNativeKitTests/PNTransactionTests.swift +91 -0
- pythonnative/native/yoga/LICENSE +21 -0
- pythonnative/native/yoga/Package.swift +9 -0
- pythonnative/native/yoga/VENDORED.md +2 -0
- pythonnative/native/yoga/include/PNStyle.h +10 -0
- pythonnative/native/yoga/include/module.modulemap +5 -0
- pythonnative/native/yoga/include/yoga/YGConfig.h +158 -0
- pythonnative/native/yoga/include/yoga/YGEnums.h +142 -0
- pythonnative/native/yoga/include/yoga/YGMacros.h +99 -0
- pythonnative/native/yoga/include/yoga/YGNode.h +304 -0
- pythonnative/native/yoga/include/yoga/YGNodeLayout.h +35 -0
- pythonnative/native/yoga/include/yoga/YGNodeStyle.h +130 -0
- pythonnative/native/yoga/include/yoga/YGPixelGrid.h +29 -0
- pythonnative/native/yoga/include/yoga/YGValue.h +84 -0
- pythonnative/native/yoga/include/yoga/Yoga.h +21 -0
- pythonnative/native/yoga/python.cpp +16 -0
- pythonnative/native/yoga/style.cpp +272 -0
- pythonnative/native/yoga/yoga/CMakeLists.txt +41 -0
- pythonnative/native/yoga/yoga/YGConfig.cpp +92 -0
- pythonnative/native/yoga/yoga/YGConfig.h +158 -0
- pythonnative/native/yoga/yoga/YGEnums.cpp +262 -0
- pythonnative/native/yoga/yoga/YGEnums.h +142 -0
- pythonnative/native/yoga/yoga/YGMacros.h +99 -0
- pythonnative/native/yoga/yoga/YGNode.cpp +366 -0
- pythonnative/native/yoga/yoga/YGNode.h +304 -0
- pythonnative/native/yoga/yoga/YGNodeLayout.cpp +92 -0
- pythonnative/native/yoga/yoga/YGNodeLayout.h +35 -0
- pythonnative/native/yoga/yoga/YGNodeStyle.cpp +405 -0
- pythonnative/native/yoga/yoga/YGNodeStyle.h +130 -0
- pythonnative/native/yoga/yoga/YGPixelGrid.cpp +22 -0
- pythonnative/native/yoga/yoga/YGPixelGrid.h +29 -0
- pythonnative/native/yoga/yoga/YGValue.cpp +20 -0
- pythonnative/native/yoga/yoga/YGValue.h +84 -0
- pythonnative/native/yoga/yoga/Yoga.h +21 -0
- pythonnative/native/yoga/yoga/algorithm/AbsoluteLayout.cpp +563 -0
- pythonnative/native/yoga/yoga/algorithm/AbsoluteLayout.h +41 -0
- pythonnative/native/yoga/yoga/algorithm/Align.h +72 -0
- pythonnative/native/yoga/yoga/algorithm/Baseline.cpp +78 -0
- pythonnative/native/yoga/yoga/algorithm/Baseline.h +21 -0
- pythonnative/native/yoga/yoga/algorithm/BoundAxis.h +79 -0
- pythonnative/native/yoga/yoga/algorithm/Cache.cpp +121 -0
- pythonnative/native/yoga/yoga/algorithm/Cache.h +30 -0
- pythonnative/native/yoga/yoga/algorithm/CalculateLayout.cpp +2428 -0
- pythonnative/native/yoga/yoga/algorithm/CalculateLayout.h +38 -0
- pythonnative/native/yoga/yoga/algorithm/FlexDirection.h +120 -0
- pythonnative/native/yoga/yoga/algorithm/FlexLine.cpp +124 -0
- pythonnative/native/yoga/yoga/algorithm/FlexLine.h +75 -0
- pythonnative/native/yoga/yoga/algorithm/PixelGrid.cpp +132 -0
- pythonnative/native/yoga/yoga/algorithm/PixelGrid.h +29 -0
- pythonnative/native/yoga/yoga/algorithm/SizingMode.h +73 -0
- pythonnative/native/yoga/yoga/algorithm/TrailingPosition.h +44 -0
- pythonnative/native/yoga/yoga/config/Config.cpp +135 -0
- pythonnative/native/yoga/yoga/config/Config.h +92 -0
- pythonnative/native/yoga/yoga/debug/AssertFatal.cpp +53 -0
- pythonnative/native/yoga/yoga/debug/AssertFatal.h +29 -0
- pythonnative/native/yoga/yoga/debug/Log.cpp +107 -0
- pythonnative/native/yoga/yoga/debug/Log.h +34 -0
- pythonnative/native/yoga/yoga/enums/Align.h +47 -0
- pythonnative/native/yoga/yoga/enums/BoxSizing.h +40 -0
- pythonnative/native/yoga/yoga/enums/Dimension.h +40 -0
- pythonnative/native/yoga/yoga/enums/Direction.h +41 -0
- pythonnative/native/yoga/yoga/enums/Display.h +41 -0
- pythonnative/native/yoga/yoga/enums/Edge.h +47 -0
- pythonnative/native/yoga/yoga/enums/Errata.h +41 -0
- pythonnative/native/yoga/yoga/enums/ExperimentalFeature.h +39 -0
- pythonnative/native/yoga/yoga/enums/FlexDirection.h +42 -0
- pythonnative/native/yoga/yoga/enums/Gutter.h +41 -0
- pythonnative/native/yoga/yoga/enums/Justify.h +44 -0
- pythonnative/native/yoga/yoga/enums/LogLevel.h +44 -0
- pythonnative/native/yoga/yoga/enums/MeasureMode.h +41 -0
- pythonnative/native/yoga/yoga/enums/NodeType.h +40 -0
- pythonnative/native/yoga/yoga/enums/Overflow.h +41 -0
- pythonnative/native/yoga/yoga/enums/PhysicalEdge.h +21 -0
- pythonnative/native/yoga/yoga/enums/PositionType.h +41 -0
- pythonnative/native/yoga/yoga/enums/Unit.h +42 -0
- pythonnative/native/yoga/yoga/enums/Wrap.h +41 -0
- pythonnative/native/yoga/yoga/enums/YogaEnums.h +85 -0
- pythonnative/native/yoga/yoga/event/event.cpp +87 -0
- pythonnative/native/yoga/yoga/event/event.h +130 -0
- pythonnative/native/yoga/yoga/module.modulemap +21 -0
- pythonnative/native/yoga/yoga/node/CachedMeasurement.h +53 -0
- pythonnative/native/yoga/yoga/node/LayoutResults.cpp +48 -0
- pythonnative/native/yoga/yoga/node/LayoutResults.h +122 -0
- pythonnative/native/yoga/yoga/node/LayoutableChildren.h +148 -0
- pythonnative/native/yoga/yoga/node/Node.cpp +443 -0
- pythonnative/native/yoga/yoga/node/Node.h +337 -0
- pythonnative/native/yoga/yoga/numeric/Comparison.h +81 -0
- pythonnative/native/yoga/yoga/numeric/FloatOptional.h +93 -0
- pythonnative/native/yoga/yoga/style/SmallValueBuffer.h +133 -0
- pythonnative/native/yoga/yoga/style/Style.h +757 -0
- pythonnative/native/yoga/yoga/style/StyleLength.h +107 -0
- pythonnative/native/yoga/yoga/style/StyleValueHandle.h +98 -0
- pythonnative/native/yoga/yoga/style/StyleValuePool.h +127 -0
- pythonnative/native_modules/__init__.py +124 -0
- pythonnative/native_modules/app_state.py +99 -0
- pythonnative/native_modules/battery.py +75 -0
- pythonnative/native_modules/biometrics.py +42 -0
- pythonnative/native_modules/camera.py +65 -0
- pythonnative/native_modules/clipboard.py +46 -0
- pythonnative/native_modules/fallback.py +324 -0
- pythonnative/native_modules/file_system.py +145 -0
- pythonnative/native_modules/haptics.py +75 -0
- pythonnative/native_modules/linking.py +123 -0
- pythonnative/native_modules/location.py +75 -0
- pythonnative/native_modules/net_info.py +112 -0
- pythonnative/native_modules/notifications.py +129 -0
- pythonnative/native_modules/permissions.py +108 -0
- pythonnative/native_modules/registry.py +526 -0
- pythonnative/native_modules/secure_store.py +52 -0
- pythonnative/native_modules/share.py +51 -0
- pythonnative/native_views/__init__.py +375 -0
- pythonnative/native_views/base.py +316 -0
- pythonnative/native_views/bridge_backend.py +277 -0
- pythonnative/navigation/__init__.py +103 -0
- pythonnative/navigation/container.py +154 -0
- pythonnative/navigation/handle.py +540 -0
- pythonnative/navigation/hooks.py +133 -0
- pythonnative/navigation/host.py +58 -0
- pythonnative/navigation/linking.py +200 -0
- pythonnative/navigation/navigators.py +659 -0
- pythonnative/navigation/screen.py +149 -0
- pythonnative/navigation/state.py +269 -0
- pythonnative/net.py +248 -0
- pythonnative/platform.py +166 -0
- pythonnative/platform_metrics.py +252 -0
- pythonnative/preview.py +287 -0
- pythonnative/profiling.py +101 -0
- pythonnative/project/__init__.py +68 -0
- pythonnative/project/android.py +557 -0
- pythonnative/project/builder.py +817 -0
- pythonnative/project/config.py +773 -0
- pythonnative/project/deps.py +719 -0
- pythonnative/project/devices.py +304 -0
- pythonnative/project/doctor.py +283 -0
- pythonnative/project/fingerprint.py +171 -0
- pythonnative/project/icons.py +247 -0
- pythonnative/project/ios.py +325 -0
- pythonnative/project/lockfile.py +100 -0
- pythonnative/project/permissions.py +361 -0
- pythonnative/project/plugins.py +519 -0
- pythonnative/project/runtime_assets.py +183 -0
- pythonnative/py.typed +0 -0
- pythonnative/query.py +153 -0
- pythonnative/reconciler/__init__.py +29 -0
- pythonnative/reconciler/boundaries.py +375 -0
- pythonnative/reconciler/children.py +88 -0
- pythonnative/reconciler/core.py +1165 -0
- pythonnative/reconciler/layout_pass.py +393 -0
- pythonnative/reconciler/vnode.py +266 -0
- pythonnative/refresh.py +55 -0
- pythonnative/runtime.py +306 -0
- pythonnative/scheduler.py +159 -0
- pythonnative/sdk/__init__.py +153 -0
- pythonnative/sdk/_components.py +427 -0
- pythonnative/sdk/builtins.py +91 -0
- pythonnative/sdk/codegen.py +149 -0
- pythonnative/sdk/module_codegen.py +184 -0
- pythonnative/sdk/schema.py +224 -0
- pythonnative/sdk/templates/contracts.kt +46 -0
- pythonnative/sdk/templates/contracts.swift +46 -0
- pythonnative/storage.py +184 -0
- pythonnative/style.py +841 -0
- pythonnative/suspense.py +183 -0
- pythonnative/templates/android_template/app/build.gradle +75 -0
- pythonnative/templates/android_template/app/proguard-rules.pro +21 -0
- pythonnative/templates/android_template/app/src/androidTest/java/com/pythonnative/android_template/ExampleInstrumentedTest.kt +24 -0
- pythonnative/templates/android_template/app/src/debug/AndroidManifest.xml +7 -0
- pythonnative/templates/android_template/app/src/main/AndroidManifest.xml +32 -0
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +130 -0
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +13 -0
- pythonnative/templates/android_template/app/src/main/res/drawable/ic_launcher_background.xml +170 -0
- pythonnative/templates/android_template/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +30 -0
- pythonnative/templates/android_template/app/src/main/res/layout/activity_main.xml +10 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +6 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +6 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
- pythonnative/templates/android_template/app/src/main/res/navigation/nav_graph.xml +22 -0
- pythonnative/templates/android_template/app/src/main/res/values/colors.xml +5 -0
- pythonnative/templates/android_template/app/src/main/res/values/strings.xml +5 -0
- pythonnative/templates/android_template/app/src/main/res/values/themes.xml +9 -0
- pythonnative/templates/android_template/app/src/main/res/values-night/themes.xml +7 -0
- pythonnative/templates/android_template/app/src/main/res/xml/backup_rules.xml +13 -0
- pythonnative/templates/android_template/app/src/main/res/xml/data_extraction_rules.xml +19 -0
- pythonnative/templates/android_template/app/src/test/java/com/pythonnative/android_template/ExampleUnitTest.kt +17 -0
- pythonnative/templates/android_template/build.gradle +10 -0
- pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.jar +0 -0
- pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.properties +6 -0
- pythonnative/templates/android_template/gradle.properties +23 -0
- pythonnative/templates/android_template/gradlew +185 -0
- pythonnative/templates/android_template/gradlew.bat +89 -0
- pythonnative/templates/android_template/settings.gradle +17 -0
- pythonnative/templates/ios_template/ios_template/AppDelegate.swift +51 -0
- pythonnative/templates/ios_template/ios_template/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
- pythonnative/templates/ios_template/ios_template/Assets.xcassets/AppIcon.appiconset/Contents.json +13 -0
- pythonnative/templates/ios_template/ios_template/Assets.xcassets/Contents.json +6 -0
- pythonnative/templates/ios_template/ios_template/Base.lproj/LaunchScreen.storyboard +25 -0
- pythonnative/templates/ios_template/ios_template/BridgingHeader.h +14 -0
- pythonnative/templates/ios_template/ios_template/Info.plist +28 -0
- pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +390 -0
- pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +61 -0
- pythonnative/templates/ios_template/ios_template/ViewController.swift +23 -0
- pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +745 -0
- pythonnative/templates/ios_template/ios_template.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- pythonnative/templates/ios_template/ios_templateTests/ios_templateTests.swift +36 -0
- pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift +41 -0
- pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITestsLaunchTests.swift +32 -0
- pythonnative/testing/__init__.py +53 -0
- pythonnative/testing/backend.py +276 -0
- pythonnative/testing/harness.py +369 -0
- pythonnative/utils.py +145 -0
- pythonnative-0.40.0.dist-info/DELVEWHEEL +2 -0
- pythonnative-0.40.0.dist-info/METADATA +150 -0
- pythonnative-0.40.0.dist-info/RECORD +418 -0
- pythonnative-0.40.0.dist-info/WHEEL +5 -0
- pythonnative-0.40.0.dist-info/entry_points.txt +2 -0
- pythonnative-0.40.0.dist-info/licenses/LICENSE +21 -0
- pythonnative-0.40.0.dist-info/top_level.txt +1 -0
- pythonnative.libs/msvcp140-a4c2229bdc2a2a630acdc095b4d86008.dll +0 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
/// Drives the `animate(tag, request)` protocol for animatable props
|
|
4
|
+
/// (`opacity`, `background_color`, `color`, `translate_x`, `translate_y`,
|
|
5
|
+
/// `scale`, `scale_x`, `scale_y`, `rotate`).
|
|
6
|
+
///
|
|
7
|
+
/// - `set` applies one Python-driven frame immediately.
|
|
8
|
+
/// - `start` runs `timing` / `spring` with `UIViewPropertyAnimator` and
|
|
9
|
+
/// `decay` with a display-link integrator; completion posts
|
|
10
|
+
/// `callback("animation", 0, "", {"id": n, "finished": bool})`.
|
|
11
|
+
/// - `cancel` stops the animation and returns the presentation value.
|
|
12
|
+
public final class PNAnimator {
|
|
13
|
+
public static let shared = PNAnimator()
|
|
14
|
+
|
|
15
|
+
/// Prop names accepted by `set` and `start`.
|
|
16
|
+
public static let animatableProps: Set<String> = [
|
|
17
|
+
"opacity", "background_color", "color", "translate_x", "translate_y", "scale", "scale_x", "scale_y", "rotate",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
final class Running {
|
|
21
|
+
weak var view: UIView?
|
|
22
|
+
let prop: String
|
|
23
|
+
var animator: UIViewPropertyAnimator?
|
|
24
|
+
var decay: PNDecayDriver?
|
|
25
|
+
init(view: UIView, prop: String) {
|
|
26
|
+
self.view = view
|
|
27
|
+
self.prop = prop
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private var running: [Int64: Running] = [:]
|
|
32
|
+
|
|
33
|
+
private init() {}
|
|
34
|
+
|
|
35
|
+
// MARK: - Request routing
|
|
36
|
+
|
|
37
|
+
/// Handle one decoded `animate` request for the view with `tag`.
|
|
38
|
+
public func handle(tag: Int64, request: [String: Any]) -> Any? {
|
|
39
|
+
let op = PNProps.string(request["op"]) ?? ""
|
|
40
|
+
switch op {
|
|
41
|
+
case "graph":
|
|
42
|
+
PNAnimationGraph.install(request["graph"] as? [String: Any] ?? [:])
|
|
43
|
+
return nil
|
|
44
|
+
case "set":
|
|
45
|
+
guard let record = PNViewRegistry.shared.resolve(tag), let prop = PNProps.string(request["prop"]) else { return nil }
|
|
46
|
+
record.manager.setAnimatedProperty(view: record.view, prop: prop, value: request["value"])
|
|
47
|
+
return nil
|
|
48
|
+
case "start":
|
|
49
|
+
guard let record = PNViewRegistry.shared.resolve(tag), let prop = PNProps.string(request["prop"]),
|
|
50
|
+
let id = PNProps.int(request["id"])
|
|
51
|
+
else { return ["ok": false] }
|
|
52
|
+
let spec = PNProps.dict(request["spec"]) ?? [:]
|
|
53
|
+
if prop.hasPrefix("_pn_graph:"), let node = Int64(prop.dropFirst(10)) {
|
|
54
|
+
return ["ok": PNAnimationGraph.start(Int64(id), node: node, spec: spec)]
|
|
55
|
+
}
|
|
56
|
+
let ok = record.manager.startAnimation(view: record.view, id: Int64(id), prop: prop, spec: spec)
|
|
57
|
+
return ["ok": ok]
|
|
58
|
+
case "cancel":
|
|
59
|
+
guard let id = PNProps.int(request["id"]) else { return nil }
|
|
60
|
+
if let value = PNAnimationGraph.cancel(Int64(id)) { return ["value": value] }
|
|
61
|
+
let value: Any?
|
|
62
|
+
if let record = PNViewRegistry.shared.resolve(tag) {
|
|
63
|
+
value = record.manager.cancelAnimation(view: record.view, id: Int64(id))
|
|
64
|
+
} else {
|
|
65
|
+
value = cancel(id: Int64(id))
|
|
66
|
+
}
|
|
67
|
+
guard let value = value else { return nil }
|
|
68
|
+
return ["value": value]
|
|
69
|
+
default:
|
|
70
|
+
PNLog.rateLimited(PNLog.animation, key: "op:\(op)", "unknown animate op '\(op)'")
|
|
71
|
+
return nil
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// MARK: - Frame application
|
|
76
|
+
|
|
77
|
+
/// Apply one value of `prop` to `view` immediately.
|
|
78
|
+
public func applyValue(view: UIView, prop: String, value: Any?) {
|
|
79
|
+
if prop.hasPrefix("_pn_graph:"), let node = Int64(prop.dropFirst(10)), let value = PNProps.double(value) {
|
|
80
|
+
PNAnimationGraph.set(node, value)
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
switch prop {
|
|
84
|
+
case "opacity":
|
|
85
|
+
if let v = PNProps.double(value) { view.alpha = CGFloat(v) }
|
|
86
|
+
case "background_color":
|
|
87
|
+
if let color = PNColor.parse(value) { view.backgroundColor = color }
|
|
88
|
+
case "color":
|
|
89
|
+
guard let color = PNColor.parse(value) else { return }
|
|
90
|
+
if let label = view as? UILabel {
|
|
91
|
+
label.textColor = color
|
|
92
|
+
} else if let field = view as? UITextField {
|
|
93
|
+
field.textColor = color
|
|
94
|
+
} else if let textView = view as? UITextView {
|
|
95
|
+
textView.textColor = color
|
|
96
|
+
} else if let button = view as? UIButton {
|
|
97
|
+
button.setTitleColor(color, for: .normal)
|
|
98
|
+
}
|
|
99
|
+
case "translate_x", "translate_y", "scale", "scale_x", "scale_y", "rotate":
|
|
100
|
+
guard let v = PNProps.double(value), v.isFinite else { return }
|
|
101
|
+
PNTransform.apply(view, spec: [[prop: v]])
|
|
102
|
+
default:
|
|
103
|
+
PNLog.once(PNLog.animation, key: "prop:\(prop)", "ignoring unknown animated prop '\(prop)'")
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/// Read the value of `prop` currently on screen (presentation layer when animating).
|
|
108
|
+
public func presentationValue(view: UIView, prop: String) -> Any? {
|
|
109
|
+
let layer = view.layer.presentation() ?? view.layer
|
|
110
|
+
switch prop {
|
|
111
|
+
case "opacity":
|
|
112
|
+
return Double(layer.opacity)
|
|
113
|
+
case "background_color":
|
|
114
|
+
return layer.backgroundColor.map { PNColor.hexString(UIColor(cgColor: $0)) }
|
|
115
|
+
case "translate_x", "translate_y", "scale", "scale_x", "scale_y", "rotate":
|
|
116
|
+
let t = layer.affineTransform()
|
|
117
|
+
let parts = PNTransform.decompose(t)
|
|
118
|
+
switch prop {
|
|
119
|
+
case "translate_x": return Double(parts.translateX)
|
|
120
|
+
case "translate_y": return Double(parts.translateY)
|
|
121
|
+
case "scale", "scale_x": return Double(parts.scaleX)
|
|
122
|
+
case "scale_y": return Double(parts.scaleY)
|
|
123
|
+
default: return Double(parts.rotateDegrees)
|
|
124
|
+
}
|
|
125
|
+
default:
|
|
126
|
+
return nil
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// MARK: - Start / cancel
|
|
131
|
+
|
|
132
|
+
/// Start a native animation. Returns `false` when the spec must be
|
|
133
|
+
/// ticked by Python instead.
|
|
134
|
+
public func start(view: UIView, id: Int64, prop: String, spec: [String: Any]) -> Bool {
|
|
135
|
+
guard PNAnimator.animatableProps.contains(prop), Thread.isMainThread else { return false }
|
|
136
|
+
let kind = PNProps.string(spec["kind"]) ?? ""
|
|
137
|
+
let entry = Running(view: view, prop: prop)
|
|
138
|
+
switch kind {
|
|
139
|
+
case "timing", "spring":
|
|
140
|
+
guard spec["to"] != nil else { return false }
|
|
141
|
+
if let from = spec["from"] { applyValue(view: view, prop: prop, value: from) }
|
|
142
|
+
let timing = PNAnimator.timingParameters(kind: kind, spec: spec)
|
|
143
|
+
let animator = UIViewPropertyAnimator(duration: timing.duration, timingParameters: timing.parameters)
|
|
144
|
+
animator.isUserInteractionEnabled = true
|
|
145
|
+
animator.addAnimations { [weak self, weak view] in
|
|
146
|
+
guard let self = self, let view = view else { return }
|
|
147
|
+
self.applyValue(view: view, prop: prop, value: spec["to"])
|
|
148
|
+
}
|
|
149
|
+
animator.addCompletion { [weak self] position in
|
|
150
|
+
guard let self = self, self.running[id] != nil else { return }
|
|
151
|
+
self.running.removeValue(forKey: id)
|
|
152
|
+
PNAnimator.reportCompletion(id: id, finished: position == .end)
|
|
153
|
+
}
|
|
154
|
+
entry.animator = animator
|
|
155
|
+
running[id] = entry
|
|
156
|
+
let delay = max(0, (PNProps.double(spec["delay_ms"]) ?? 0) / 1000)
|
|
157
|
+
animator.startAnimation(afterDelay: delay)
|
|
158
|
+
return true
|
|
159
|
+
case "decay":
|
|
160
|
+
guard let from = PNProps.double(spec["from"]), let velocity = PNProps.double(spec["velocity"]) else { return false }
|
|
161
|
+
let deceleration = max(1e-6, PNProps.double(spec["deceleration"]) ?? 0.997)
|
|
162
|
+
let driver = PNDecayDriver(from: from, velocity: velocity, deceleration: deceleration) { [weak self, weak view] value in
|
|
163
|
+
guard let self = self, let view = view else { return }
|
|
164
|
+
self.applyValue(view: view, prop: prop, value: value)
|
|
165
|
+
} completion: { [weak self] in
|
|
166
|
+
guard let self = self, self.running[id] != nil else { return }
|
|
167
|
+
self.running.removeValue(forKey: id)
|
|
168
|
+
PNAnimator.reportCompletion(id: id, finished: true)
|
|
169
|
+
}
|
|
170
|
+
entry.decay = driver
|
|
171
|
+
running[id] = entry
|
|
172
|
+
driver.start()
|
|
173
|
+
return true
|
|
174
|
+
default:
|
|
175
|
+
return false
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/// Stop animation `id`, leaving the view at its presentation value.
|
|
180
|
+
/// Returns that value (or `nil` when the animation isn't running).
|
|
181
|
+
public func cancel(id: Int64) -> Any? {
|
|
182
|
+
guard let entry = running.removeValue(forKey: id) else { return nil }
|
|
183
|
+
var value: Any?
|
|
184
|
+
if let view = entry.view {
|
|
185
|
+
value = presentationValue(view: view, prop: entry.prop)
|
|
186
|
+
}
|
|
187
|
+
if let animator = entry.animator {
|
|
188
|
+
animator.stopAnimation(true)
|
|
189
|
+
}
|
|
190
|
+
if let decay = entry.decay {
|
|
191
|
+
value = decay.currentValue
|
|
192
|
+
decay.stop()
|
|
193
|
+
}
|
|
194
|
+
if let view = entry.view, let value = value {
|
|
195
|
+
view.layer.removeAllAnimations()
|
|
196
|
+
applyValue(view: view, prop: entry.prop, value: value)
|
|
197
|
+
}
|
|
198
|
+
return value
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/// Drop bookkeeping for animations targeting `view` (on destroy).
|
|
202
|
+
public func forget(view: UIView) {
|
|
203
|
+
for (id, entry) in running where entry.view === view || entry.view == nil {
|
|
204
|
+
entry.animator?.stopAnimation(true)
|
|
205
|
+
entry.decay?.stop()
|
|
206
|
+
running.removeValue(forKey: id)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// MARK: - Helpers
|
|
211
|
+
|
|
212
|
+
static func reportCompletion(id: Int64, finished: Bool) {
|
|
213
|
+
PNBridge.shared.callPython(kind: "animation", tag: 0, name: "", payload: PNJSON.encode(["id": id, "finished": finished]))
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/// Resolve `timing` / `spring` specs to UIKit timing parameters.
|
|
217
|
+
static func timingParameters(kind: String, spec: [String: Any]) -> (duration: TimeInterval, parameters: UITimingCurveProvider) {
|
|
218
|
+
if kind == "spring" {
|
|
219
|
+
let stiffness = max(1e-3, PNProps.double(spec["stiffness"]) ?? 100)
|
|
220
|
+
let damping = max(1e-3, PNProps.double(spec["damping"]) ?? 10)
|
|
221
|
+
let mass = max(1e-3, PNProps.double(spec["mass"]) ?? 1)
|
|
222
|
+
let omega0 = sqrt(stiffness / mass)
|
|
223
|
+
let zeta = damping / (2 * sqrt(stiffness * mass))
|
|
224
|
+
let duration = zeta < 1 ? min(10, max(0.15, 4 / max(0.05, zeta * omega0))) : min(10, max(0.15, 4 / omega0))
|
|
225
|
+
let distance = abs((PNProps.double(spec["to"]) ?? 0) - (PNProps.double(spec["from"]) ?? 0))
|
|
226
|
+
let v0 = PNProps.double(spec["initial_velocity"]) ?? 0
|
|
227
|
+
let normalized = distance > 1e-9 ? v0 / distance : 0
|
|
228
|
+
let parameters = UISpringTimingParameters(
|
|
229
|
+
mass: CGFloat(mass), stiffness: CGFloat(stiffness), damping: CGFloat(damping),
|
|
230
|
+
initialVelocity: CGVector(dx: normalized, dy: normalized)
|
|
231
|
+
)
|
|
232
|
+
return (duration, parameters)
|
|
233
|
+
}
|
|
234
|
+
let duration = max(0, (PNProps.double(spec["duration_ms"]) ?? 300) / 1000)
|
|
235
|
+
return (duration, curve(for: spec["easing"]))
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/// Map an easing name (or `[x1, y1, x2, y2]` control points) to a timing curve.
|
|
239
|
+
static func curve(for easing: Any?) -> UITimingCurveProvider {
|
|
240
|
+
if let points = easing as? [Any], points.count == 4 {
|
|
241
|
+
let values = points.compactMap { PNProps.double($0) }
|
|
242
|
+
if values.count == 4 {
|
|
243
|
+
return UICubicTimingParameters(
|
|
244
|
+
controlPoint1: CGPoint(x: values[0], y: values[1]), controlPoint2: CGPoint(x: values[2], y: values[3])
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
switch PNProps.string(easing) ?? "ease_in_out" {
|
|
249
|
+
case "linear": return UICubicTimingParameters(animationCurve: .linear)
|
|
250
|
+
case "ease_in", "ease_in_quad": return UICubicTimingParameters(animationCurve: .easeIn)
|
|
251
|
+
case "ease_out", "ease_out_quad": return UICubicTimingParameters(animationCurve: .easeOut)
|
|
252
|
+
case "ease", "ease_in_out": return UICubicTimingParameters(animationCurve: .easeInOut)
|
|
253
|
+
case "bounce":
|
|
254
|
+
// No CoreAnimation equivalent; approximate with an overshoot curve.
|
|
255
|
+
return UICubicTimingParameters(controlPoint1: CGPoint(x: 0.34, y: 1.56), controlPoint2: CGPoint(x: 0.64, y: 1))
|
|
256
|
+
default: return UICubicTimingParameters(animationCurve: .easeInOut)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/// Integrates the `decay` model on a `CADisplayLink`, mirroring the
|
|
262
|
+
/// Python ticker: `v(t) = v0 * e^(-k * 1000 * t)` with `t` in seconds and
|
|
263
|
+
/// `v0` in units per second, so the closed-form position is
|
|
264
|
+
/// `from + v0 / (k * 1000) * (1 - e^(-k * 1000 * t))`.
|
|
265
|
+
final class PNDecayDriver {
|
|
266
|
+
private(set) var currentValue: Double
|
|
267
|
+
private let from: Double
|
|
268
|
+
private let velocity0: Double
|
|
269
|
+
private let k: Double
|
|
270
|
+
private let frame: (Double) -> Void
|
|
271
|
+
private let completion: () -> Void
|
|
272
|
+
private var link: CADisplayLink?
|
|
273
|
+
private var startTime: CFTimeInterval = 0
|
|
274
|
+
private let restVelocity = 0.001
|
|
275
|
+
|
|
276
|
+
init(from: Double, velocity: Double, deceleration: Double, frame: @escaping (Double) -> Void, completion: @escaping () -> Void) {
|
|
277
|
+
self.from = from
|
|
278
|
+
currentValue = from
|
|
279
|
+
velocity0 = velocity
|
|
280
|
+
k = deceleration
|
|
281
|
+
self.frame = frame
|
|
282
|
+
self.completion = completion
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
func start() {
|
|
286
|
+
startTime = CACurrentMediaTime()
|
|
287
|
+
if abs(velocity0) < restVelocity {
|
|
288
|
+
completion()
|
|
289
|
+
return
|
|
290
|
+
}
|
|
291
|
+
let link = CADisplayLink(target: self, selector: #selector(tick(_:)))
|
|
292
|
+
link.add(to: .main, forMode: .common)
|
|
293
|
+
self.link = link
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
func stop() {
|
|
297
|
+
link?.invalidate()
|
|
298
|
+
link = nil
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
@objc private func tick(_ link: CADisplayLink) {
|
|
302
|
+
let elapsed = max(0, link.targetTimestamp - startTime)
|
|
303
|
+
let decayFactor = exp(-k * 1000 * elapsed)
|
|
304
|
+
let velocity = velocity0 * decayFactor
|
|
305
|
+
currentValue = from + velocity0 / (k * 1000) * (1 - decayFactor)
|
|
306
|
+
frame(currentValue)
|
|
307
|
+
if abs(velocity) < restVelocity {
|
|
308
|
+
stop()
|
|
309
|
+
completion()
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
/// The version of the wire protocol compiled into this library. Python
|
|
5
|
+
/// refuses to start when `pythonnative.bridge.PROTOCOL_VERSION` differs.
|
|
6
|
+
public let PNProtocolVersion: Int32 = 2
|
|
7
|
+
|
|
8
|
+
/// The C signature Python registers through `pn_bridge_set_callback`.
|
|
9
|
+
///
|
|
10
|
+
/// `(kind, tag, name, payload_json) -> optional JSON string owned by
|
|
11
|
+
/// Python and valid until the next callback returns`.
|
|
12
|
+
public typealias PNCallbackFn = @convention(c) (
|
|
13
|
+
UnsafePointer<CChar>?, Int64, UnsafePointer<CChar>?, UnsafePointer<CChar>?
|
|
14
|
+
) -> UnsafePointer<CChar>?
|
|
15
|
+
|
|
16
|
+
/// Swift-side entry point for the reverse direction (native -> Python) and
|
|
17
|
+
/// the owner of the registered C callback.
|
|
18
|
+
public final class PNBridge {
|
|
19
|
+
public static let shared = PNBridge()
|
|
20
|
+
|
|
21
|
+
private let pythonQueue = DispatchQueue(label: "dev.pythonnative.events")
|
|
22
|
+
private struct Message {
|
|
23
|
+
let kind: String; let tag: Int64; let name: String; let payload: String
|
|
24
|
+
var continuous: Bool {
|
|
25
|
+
guard kind == "event" else { return false }
|
|
26
|
+
if ["on_scroll", "on_selection_change", "on_gesture_update"].contains(name) { return true }
|
|
27
|
+
if name.hasPrefix("gesture:"), let args = PNJSON.decodeObject(payload)["args"] as? [[String: Any]] {
|
|
28
|
+
return args.first?["state"] as? String == "changed"
|
|
29
|
+
}
|
|
30
|
+
return false
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
private let mailboxLock = NSLock()
|
|
34
|
+
private var mailbox: [Message] = []
|
|
35
|
+
private var draining = false
|
|
36
|
+
private var callback: PNCallbackFn?
|
|
37
|
+
private var registrationWaiters: [() -> Void] = []
|
|
38
|
+
|
|
39
|
+
private init() {
|
|
40
|
+
// Reference every exported entry point so the linker never
|
|
41
|
+
// dead-strips them out of the host executable; Python resolves
|
|
42
|
+
// them through `dlsym` at runtime.
|
|
43
|
+
_ = PNBridge.exportedSymbols
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Whether Python has registered its callback yet.
|
|
47
|
+
public var hasCallback: Bool { callback != nil }
|
|
48
|
+
|
|
49
|
+
/// Install (or clear) the Python callback. Runs any pending
|
|
50
|
+
/// `whenCallbackRegistered` blocks when a callback becomes available.
|
|
51
|
+
public func setCallback(_ fn: PNCallbackFn?) {
|
|
52
|
+
callback = fn
|
|
53
|
+
guard fn != nil else { return }
|
|
54
|
+
let waiters = registrationWaiters
|
|
55
|
+
registrationWaiters = []
|
|
56
|
+
for waiter in waiters { waiter() }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// Run `block` immediately if the callback is registered, otherwise
|
|
60
|
+
/// once it is. Used to buffer deep links and other early events.
|
|
61
|
+
public func whenCallbackRegistered(_ block: @escaping () -> Void) {
|
|
62
|
+
if callback != nil {
|
|
63
|
+
block()
|
|
64
|
+
} else {
|
|
65
|
+
registrationWaiters.append(block)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Invoke the Python callback. Returns the JSON string Python returned
|
|
70
|
+
/// (copied immediately), or `nil` when Python returned NULL or no
|
|
71
|
+
/// callback is registered.
|
|
72
|
+
@discardableResult
|
|
73
|
+
public func callPython(kind: String, tag: Int64, name: String, payload: String) -> String? {
|
|
74
|
+
let message = Message(kind: kind, tag: tag, name: name, payload: payload)
|
|
75
|
+
mailboxLock.lock()
|
|
76
|
+
if message.continuous, let last = mailbox.last, last.continuous,
|
|
77
|
+
last.tag == tag, last.name == name {
|
|
78
|
+
mailbox[mailbox.count - 1] = message
|
|
79
|
+
} else { mailbox.append(message) }
|
|
80
|
+
let schedule = !draining
|
|
81
|
+
draining = true
|
|
82
|
+
mailboxLock.unlock()
|
|
83
|
+
if schedule { pythonQueue.async { [self] in drainMailbox() } }
|
|
84
|
+
return nil
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private func drainMailbox() {
|
|
88
|
+
while true {
|
|
89
|
+
mailboxLock.lock()
|
|
90
|
+
if mailbox.isEmpty { draining = false; mailboxLock.unlock(); return }
|
|
91
|
+
let message = mailbox.removeFirst()
|
|
92
|
+
mailboxLock.unlock()
|
|
93
|
+
guard let callback = callback else { continue }
|
|
94
|
+
message.kind.withCString { kind in
|
|
95
|
+
message.name.withCString { name in
|
|
96
|
+
message.payload.withCString { payload in
|
|
97
|
+
_ = callback(kind, message.tag, name, payload)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
static func onUI<T>(_ body: () -> T) -> T {
|
|
105
|
+
if Thread.isMainThread { return body() }
|
|
106
|
+
return DispatchQueue.main.sync(execute: body)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Emit a view event: `callback("event", tag, name, args_array_json)`.
|
|
110
|
+
@discardableResult
|
|
111
|
+
public func emitEvent(tag: Int64, name: String, args: [Any?]) -> String? {
|
|
112
|
+
PNAnimationGraph.event(tag, name, args)
|
|
113
|
+
var editRevision = 0
|
|
114
|
+
if name == "on_change", let view = PNViewRegistry.shared.view(for: tag),
|
|
115
|
+
view is UITextInput, let state = PNViewState.existing(for: view) {
|
|
116
|
+
editRevision = (state.extras["edit_revision"] as? Int ?? 0) + 1
|
|
117
|
+
state.extras["edit_revision"] = editRevision
|
|
118
|
+
}
|
|
119
|
+
return callPython(kind: "event", tag: tag, name: name, payload: PNCommit.event(args, editRevision: editRevision))
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/// Log when an entry point is reached off the main thread. Python
|
|
123
|
+
/// always calls from the main thread, so this only guards misuse.
|
|
124
|
+
static func ensureMainThread(_ context: String) {
|
|
125
|
+
if !Thread.isMainThread {
|
|
126
|
+
PNLog.rateLimited(PNLog.bridge, key: "off-main:\(context)", "\(context) called off the main thread; running inline")
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/// Copy a Swift string into a `strdup`'d buffer Python frees via `pn_bridge_free`.
|
|
131
|
+
static func duplicate(_ string: String?) -> UnsafeMutablePointer<CChar>? {
|
|
132
|
+
guard let string = string else { return nil }
|
|
133
|
+
return strdup(string)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private static let exportedSymbols: [Any] = [
|
|
137
|
+
pn_bridge_apply as Any,
|
|
138
|
+
pn_bridge_measure as Any,
|
|
139
|
+
pn_bridge_command as Any,
|
|
140
|
+
pn_bridge_animate as Any,
|
|
141
|
+
pn_bridge_call as Any,
|
|
142
|
+
pn_bridge_free as Any,
|
|
143
|
+
pn_bridge_set_callback as Any,
|
|
144
|
+
pn_bridge_protocol_version as Any,
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// MARK: - C exports
|
|
149
|
+
|
|
150
|
+
/// Apply one serialized transaction (a JSON array of ops).
|
|
151
|
+
@_cdecl("pn_bridge_apply")
|
|
152
|
+
public func pn_bridge_apply(_ transactionJSON: UnsafePointer<CChar>?) -> UnsafeMutablePointer<CChar>? {
|
|
153
|
+
guard let transactionJSON = transactionJSON else { return nil }
|
|
154
|
+
let json = String(cString: transactionJSON)
|
|
155
|
+
return PNBridge.onUI { PNBridge.duplicate(PNCommit.apply(json)) }
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/// Measure the view with `tag` under the given constraints (`1e6` means unconstrained).
|
|
159
|
+
@_cdecl("pn_bridge_measure")
|
|
160
|
+
public func pn_bridge_measure(
|
|
161
|
+
_ tag: Int64, _ maxW: Double, _ maxH: Double,
|
|
162
|
+
_ outW: UnsafeMutablePointer<Double>?, _ outH: UnsafeMutablePointer<Double>?
|
|
163
|
+
) {
|
|
164
|
+
PNBridge.onUI {
|
|
165
|
+
|
|
166
|
+
PNBridge.ensureMainThread("pn_bridge_measure")
|
|
167
|
+
var size = CGSize.zero
|
|
168
|
+
if let record = PNViewRegistry.shared.resolve(tag) {
|
|
169
|
+
size = record.manager.measure(view: record.view, maxW: CGFloat(maxW), maxH: CGFloat(maxH))
|
|
170
|
+
}
|
|
171
|
+
outW?.pointee = size.width.isFinite ? Double(size.width) : 0
|
|
172
|
+
outH?.pointee = size.height.isFinite ? Double(size.height) : 0
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/// Run an imperative command on one view; returns an optional JSON result.
|
|
177
|
+
@_cdecl("pn_bridge_command")
|
|
178
|
+
public func pn_bridge_command(
|
|
179
|
+
_ tag: Int64, _ name: UnsafePointer<CChar>?, _ argsJSON: UnsafePointer<CChar>?
|
|
180
|
+
) -> UnsafeMutablePointer<CChar>? {
|
|
181
|
+
return PNBridge.onUI {
|
|
182
|
+
|
|
183
|
+
PNBridge.ensureMainThread("pn_bridge_command")
|
|
184
|
+
guard let name = name, let record = PNViewRegistry.shared.resolve(tag) else { return nil }
|
|
185
|
+
let args = PNJSON.decodeObject(argsJSON.map { String(cString: $0) })
|
|
186
|
+
let result = record.manager.command(view: record.view, name: String(cString: name), args: args)
|
|
187
|
+
guard let result = result else { return nil }
|
|
188
|
+
return PNBridge.duplicate(PNJSON.encode(result))
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/// Drive a view's animatable properties (`set` / `start` / `cancel`).
|
|
193
|
+
@_cdecl("pn_bridge_animate")
|
|
194
|
+
public func pn_bridge_animate(
|
|
195
|
+
_ tag: Int64, _ requestJSON: UnsafePointer<CChar>?
|
|
196
|
+
) -> UnsafeMutablePointer<CChar>? {
|
|
197
|
+
return PNBridge.onUI {
|
|
198
|
+
|
|
199
|
+
PNBridge.ensureMainThread("pn_bridge_animate")
|
|
200
|
+
let request = PNJSON.decodeObject(requestJSON.map { String(cString: $0) })
|
|
201
|
+
let result = PNAnimator.shared.handle(tag: tag, request: request)
|
|
202
|
+
guard let result = result else { return nil }
|
|
203
|
+
return PNBridge.duplicate(PNJSON.encode(result))
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/// Call a native module method. The result is `{"ok":..}`, `{"pending":true}`, or an error envelope.
|
|
208
|
+
@_cdecl("pn_bridge_call")
|
|
209
|
+
public func pn_bridge_call(
|
|
210
|
+
_ module: UnsafePointer<CChar>?, _ method: UnsafePointer<CChar>?, _ argsJSON: UnsafePointer<CChar>?
|
|
211
|
+
) -> UnsafeMutablePointer<CChar>? {
|
|
212
|
+
return PNBridge.onUI {
|
|
213
|
+
|
|
214
|
+
PNBridge.ensureMainThread("pn_bridge_call")
|
|
215
|
+
guard let module = module, let method = method else {
|
|
216
|
+
return PNBridge.duplicate(PNJSON.encode(["ok": false, "error": "missing module or method"]))
|
|
217
|
+
}
|
|
218
|
+
let envelope = PNJSON.decodeObject(argsJSON.map { String(cString: $0) })
|
|
219
|
+
if String(cString: module) == "Runtime" {
|
|
220
|
+
return PNBridge.duplicate(PNJSON.encode(["ok": true, "value": [
|
|
221
|
+
"protocol": 2, "yoga": "3.2.1", "schema": PNContracts.fingerprint,
|
|
222
|
+
"animation_graph": true, "logical_lists": true, "native_layout": true]]))
|
|
223
|
+
}
|
|
224
|
+
if String(cString: module) == "Layout" {
|
|
225
|
+
return PNBridge.duplicate(PNJSON.encode(["ok": true, "value": PNLayout.compute(envelope["args"] as? [String: Any] ?? [:])]))
|
|
226
|
+
}
|
|
227
|
+
let result = PNModuleDispatcher.shared.call(
|
|
228
|
+
module: String(cString: module), method: String(cString: method), envelope: envelope
|
|
229
|
+
)
|
|
230
|
+
return PNBridge.duplicate(PNJSON.encode(result))
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/// Free a string previously returned by a `pn_bridge_*` function.
|
|
235
|
+
@_cdecl("pn_bridge_free")
|
|
236
|
+
public func pn_bridge_free(_ ptr: UnsafeMutablePointer<CChar>?) {
|
|
237
|
+
free(ptr)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/// Register the Python callback used for every native -> Python message.
|
|
241
|
+
@_cdecl("pn_bridge_set_callback")
|
|
242
|
+
public func pn_bridge_set_callback(_ fn: PNCallbackFn?) {
|
|
243
|
+
PNBridge.onUI {
|
|
244
|
+
|
|
245
|
+
PNBridge.ensureMainThread("pn_bridge_set_callback")
|
|
246
|
+
PNRegistry.shared.ensureBuiltins()
|
|
247
|
+
PNBridge.shared.setCallback(fn)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/// The protocol version this library speaks.
|
|
252
|
+
@_cdecl("pn_bridge_protocol_version")
|
|
253
|
+
public func pn_bridge_protocol_version() -> Int32 {
|
|
254
|
+
PNProtocolVersion
|
|
255
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// Validates complete surface transactions before invoking a view manager.
|
|
4
|
+
enum PNCommit {
|
|
5
|
+
private static var application = ""
|
|
6
|
+
private static var surface = 0
|
|
7
|
+
private static var revision = 0
|
|
8
|
+
private static var parents: [Int64: Int64] = [:]
|
|
9
|
+
private static var live: Set<Int64> = []
|
|
10
|
+
private static var types: [Int64: String] = [:]
|
|
11
|
+
private static var failed = false
|
|
12
|
+
|
|
13
|
+
private static var sequence = 0
|
|
14
|
+
static func event(_ args: [Any?], editRevision: Int = 0) -> String {
|
|
15
|
+
sequence += 1
|
|
16
|
+
return PNJSON.encode(["application": application, "surface": surface,
|
|
17
|
+
"revision": revision, "sequence": sequence, "args": args, "edit_revision": editRevision])
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static func apply(_ json: String) -> String {
|
|
21
|
+
let envelope = PNJSON.decodeObject(json)
|
|
22
|
+
let app = envelope["application"] as? String ?? ""
|
|
23
|
+
let target = envelope["surface"] as? Int ?? 0
|
|
24
|
+
let next = envelope["revision"] as? Int ?? 0
|
|
25
|
+
func error(_ message: String) -> String {
|
|
26
|
+
PNJSON.encode(["ok": false, "application": app, "surface": target,
|
|
27
|
+
"revision": next, "error": message])
|
|
28
|
+
}
|
|
29
|
+
guard envelope["version"] as? Int == 2, !app.isEmpty, target > 0,
|
|
30
|
+
let raw = envelope["ops"] as? [[Any]] else { return error("invalid v2 envelope") }
|
|
31
|
+
let replacing = app != application
|
|
32
|
+
guard next == (replacing ? 1 : revision + 1), replacing || (!failed && target == surface)
|
|
33
|
+
else { return error("stale revision or failed surface") }
|
|
34
|
+
var tags: Set<Int64> = replacing ? [] : live
|
|
35
|
+
var links: [Int64: Int64] = replacing ? [:] : parents
|
|
36
|
+
var names: [Int64: String] = replacing ? [:] : types
|
|
37
|
+
var decoded: [PNTransaction.Op] = []
|
|
38
|
+
do {
|
|
39
|
+
for (index, parts) in raw.enumerated() {
|
|
40
|
+
guard let code = parts.first as? String,
|
|
41
|
+
parts.count == ["c": 4, "u": 3, "i": 4, "d": 2, "f": 6][code],
|
|
42
|
+
let number = parts[1] as? NSNumber, number.doubleValue > 0,
|
|
43
|
+
number.doubleValue.rounded() == number.doubleValue else {
|
|
44
|
+
throw PNTransaction.DecodeError.malformedOp(index: index)
|
|
45
|
+
}
|
|
46
|
+
let tag = number.int64Value
|
|
47
|
+
if code == "c" {
|
|
48
|
+
guard !tags.contains(tag), let type = parts[2] as? String, !type.isEmpty,
|
|
49
|
+
parts[3] is [String: Any] else { return error("invalid create") }
|
|
50
|
+
guard PNRegistry.shared.componentNames.contains(type) else { return error("unknown component") }
|
|
51
|
+
guard PNContracts.validate(type, parts[3] as! [String: Any]) else { return error("invalid typed props") }
|
|
52
|
+
tags.insert(tag)
|
|
53
|
+
names[tag] = type
|
|
54
|
+
} else {
|
|
55
|
+
guard tags.contains(tag) else { return error("unknown tag") }
|
|
56
|
+
if code == "u" {
|
|
57
|
+
guard let props = parts[2] as? [String: Any] else { return error("invalid props") }
|
|
58
|
+
if let type = names[tag], !PNContracts.validate(type, props, partial: true) { return error("invalid typed update") }
|
|
59
|
+
}
|
|
60
|
+
if code == "i" {
|
|
61
|
+
guard let child = parts[2] as? Int64, tags.contains(child),
|
|
62
|
+
let position = parts[3] as? Int, position >= 0 else { return error("invalid insertion") }
|
|
63
|
+
var ancestor: Int64? = tag
|
|
64
|
+
while let current = ancestor {
|
|
65
|
+
if current == child { return error("cycle") }
|
|
66
|
+
ancestor = links[current]
|
|
67
|
+
}
|
|
68
|
+
let siblings = links.filter { $0.value == tag && $0.key != child }.count
|
|
69
|
+
guard position <= siblings else { return error("insertion index exceeds child count") }
|
|
70
|
+
links[child] = tag
|
|
71
|
+
}
|
|
72
|
+
if code == "d" {
|
|
73
|
+
guard !links.values.contains(tag) else { return error("destroy children first") }
|
|
74
|
+
tags.remove(tag)
|
|
75
|
+
names.removeValue(forKey: tag)
|
|
76
|
+
links.removeValue(forKey: tag)
|
|
77
|
+
}
|
|
78
|
+
if code == "f" {
|
|
79
|
+
for value in parts[2...] {
|
|
80
|
+
guard let number = value as? NSNumber, number.doubleValue.isFinite else { return error("invalid frame") }
|
|
81
|
+
}
|
|
82
|
+
guard (parts[4] as! NSNumber).doubleValue >= 0,
|
|
83
|
+
(parts[5] as! NSNumber).doubleValue >= 0 else { return error("negative size") }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
decoded.append(try PNTransaction.decodeOp(parts, index: index))
|
|
87
|
+
}
|
|
88
|
+
} catch { return PNJSON.encode(["ok": false, "error": String(describing: error)]) }
|
|
89
|
+
if replacing {
|
|
90
|
+
for tag in live { PNTransaction.apply([.destroy(tag: tag)]) }
|
|
91
|
+
PNLayout.reset()
|
|
92
|
+
}
|
|
93
|
+
PNTransaction.apply(decoded)
|
|
94
|
+
PNLayout.observe(decoded)
|
|
95
|
+
application = app
|
|
96
|
+
surface = target
|
|
97
|
+
revision = next
|
|
98
|
+
live = tags
|
|
99
|
+
types = names
|
|
100
|
+
parents = links
|
|
101
|
+
failed = false
|
|
102
|
+
return PNJSON.encode(["ok": true, "application": app, "surface": target, "revision": next])
|
|
103
|
+
}
|
|
104
|
+
}
|