pythonnative 0.35.0__py3-none-any.whl → 0.37.0__py3-none-any.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 +6 -4
- pythonnative/_ios_log.py +1 -1
- pythonnative/alerts.py +34 -88
- pythonnative/bootstrap.py +90 -0
- pythonnative/bridge/__init__.py +306 -0
- pythonnative/bridge/android.py +78 -0
- pythonnative/bridge/codec.py +164 -0
- pythonnative/bridge/fake.py +236 -0
- pythonnative/bridge/ios.py +138 -0
- pythonnative/cli/pn.py +24 -6
- pythonnative/components/layout.py +65 -3
- pythonnative/gestures.py +13 -10
- pythonnative/hooks.py +41 -26
- pythonnative/hosts/__init__.py +13 -36
- pythonnative/hosts/native.py +348 -0
- pythonnative/images.py +6 -5
- pythonnative/mutations.py +21 -0
- pythonnative/native_modules/__init__.py +30 -29
- pythonnative/native_modules/app_state.py +10 -6
- pythonnative/native_modules/battery.py +20 -84
- pythonnative/native_modules/biometrics.py +11 -124
- pythonnative/native_modules/camera.py +15 -291
- pythonnative/native_modules/clipboard.py +14 -109
- pythonnative/native_modules/desktop.py +322 -0
- pythonnative/native_modules/file_system.py +15 -21
- pythonnative/native_modules/haptics.py +19 -129
- pythonnative/native_modules/linking.py +24 -137
- pythonnative/native_modules/location.py +28 -180
- pythonnative/native_modules/net_info.py +25 -135
- pythonnative/native_modules/notifications.py +36 -257
- pythonnative/native_modules/permissions.py +20 -184
- pythonnative/native_modules/registry.py +513 -0
- pythonnative/native_modules/secure_store.py +18 -155
- pythonnative/native_modules/share.py +8 -113
- pythonnative/native_views/__init__.py +67 -86
- pythonnative/native_views/bridge_backend.py +264 -0
- pythonnative/platform.py +19 -15
- pythonnative/platform_metrics.py +3 -3
- pythonnative/project/android.py +0 -3
- pythonnative/project/builder.py +38 -16
- pythonnative/project/config.py +12 -2
- pythonnative/project/plugins.py +366 -0
- pythonnative/py.typed +0 -0
- pythonnative/runtime.py +35 -203
- pythonnative/sdk/__init__.py +48 -33
- pythonnative/sdk/_components.py +57 -76
- pythonnative/storage.py +18 -243
- pythonnative/templates/android_template/app/build.gradle +6 -9
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +34 -166
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +13 -151
- pythonnative/templates/android_template/pythonnative/build.gradle +48 -0
- pythonnative/templates/android_template/pythonnative/src/main/AndroidManifest.xml +11 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PNBridge.kt +214 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PythonHost.kt +20 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/AnimationSpecs.kt +141 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/PNAnimator.kt +249 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/JsonUtil.kt +175 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/MainThread.kt +32 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNLog.kt +33 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNRegistry.kt +82 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNTransaction.kt +80 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/TransactionApplier.kt +115 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/ViewRegistry.kt +74 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/BuiltinComponents.kt +35 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ButtonManager.kt +33 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ComponentManager.kt +213 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ContainerManagers.kt +69 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ControlManagers.kt +186 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/DatePickerManager.kt +123 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageLoader.kt +141 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageManager.kt +156 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ModalManager.kt +134 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PNColor.kt +201 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PickerManager.kt +87 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PortalManager.kt +55 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PressableManager.kt +122 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ScrollViewManager.kt +214 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/SegmentedControlManager.kt +113 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/StatusBarManager.kt +52 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TabBarManager.kt +131 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextInputManager.kt +245 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextManager.kt +233 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ViewStyler.kt +355 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/VirtualListManager.kt +179 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/WebViewManager.kt +149 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureArbiter.kt +233 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureCoordinator.kt +127 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureRecognizers.kt +527 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/BuiltinModules.kt +73 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/DeviceModules.kt +316 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/HostModule.kt +97 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/MediaModules.kt +200 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NativeModule.kt +135 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NotificationsModule.kt +100 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/PermissionsModule.kt +88 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/StorageModules.kt +89 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/SystemModules.kt +178 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/plugins/GeneratedPlugins.kt +16 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/Navigator.kt +116 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/PNScreenFragment.kt +223 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/ScreenRegistry.kt +80 -0
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNAccessibilityDelegate.kt +5 -4
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNBorderDrawable.kt +12 -3
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/views/PNEditText.kt +18 -0
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNFrameLayout.kt +9 -10
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNVirtualListView.java +9 -12
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/AnimationSpecsTest.kt +75 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/GestureArbiterTest.kt +197 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNColorTest.kt +47 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNTransactionTest.kt +78 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PromiseTest.kt +75 -0
- pythonnative/templates/android_template/settings.gradle +1 -0
- pythonnative/templates/ios_template/PythonNativeKit/Package.swift +21 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Animation/PNAnimator.swift +301 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNBridge.swift +191 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNRegistry.swift +140 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNTransaction.swift +165 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNViewRegistry.swift +53 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNButtonManagers.swift +206 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNColor.swift +173 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNComponentManager.swift +183 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNContainerView.swift +56 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNControlManagers.swift +281 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNImageManager.swift +221 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNOverlayManagers.swift +243 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNPressableManager.swift +114 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNScrollViewManager.swift +197 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTabBarManager.swift +106 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextInputManager.swift +289 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextManager.swift +230 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTransform.swift +82 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewManager.swift +86 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewState.swift +87 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewStyler.swift +340 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNVirtualListManager.swift +257 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNWebViewManager.swift +124 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Gestures/PNGestureCoordinator.swift +260 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/AlertModule.swift +92 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/HostModule.swift +158 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/LifecycleModules.swift +174 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/MediaModules.swift +177 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PNNativeModule.swift +125 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PermissionModules.swift +251 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/SystemModules.swift +251 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Plugins/PNPluginRegistration.swift +8 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNScreenRegistry.swift +46 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNViewController.swift +251 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNCompat.swift +46 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNJSON.swift +173 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNLog.swift +47 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNWindow.swift +51 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNColorTests.swift +50 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNGestureTests.swift +101 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNManagerTests.swift +124 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNModuleTests.swift +98 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNTransactionTests.swift +91 -0
- pythonnative/templates/ios_template/ios_template/AppDelegate.swift +8 -50
- pythonnative/templates/ios_template/ios_template/Info.plist +4 -4
- pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +38 -46
- pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +11 -16
- pythonnative/templates/ios_template/ios_template/ViewController.swift +12 -142
- pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +47 -1
- pythonnative/utils.py +4 -82
- pythonnative/virtual_rows.py +3 -3
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/METADATA +3 -5
- pythonnative-0.37.0.dist-info/RECORD +263 -0
- pythonnative/hosts/android.py +0 -269
- pythonnative/hosts/ios.py +0 -404
- pythonnative/native_views/android.py +0 -3702
- pythonnative/native_views/ios.py +0 -5253
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -43
- pythonnative-0.35.0.dist-info/RECORD +0 -155
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/WHEEL +0 -0
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/top_level.txt +0 -0
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
// ios_template
|
|
4
4
|
//
|
|
5
5
|
// Creates the window programmatically and forwards scene-level events
|
|
6
|
-
// (deep links, foreground/background transitions) to the
|
|
6
|
+
// (deep links, foreground/background transitions) to the Swift native
|
|
7
|
+
// modules, which relay them to Python over the bridge.
|
|
7
8
|
//
|
|
8
9
|
|
|
10
|
+
import PythonNativeKit
|
|
9
11
|
import UIKit
|
|
10
12
|
|
|
11
13
|
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|
@@ -18,10 +20,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|
|
18
20
|
) {
|
|
19
21
|
guard let windowScene = (scene as? UIWindowScene) else { return }
|
|
20
22
|
// A cold start from a deep link delivers the URL here, before
|
|
21
|
-
// Python is running;
|
|
22
|
-
//
|
|
23
|
+
// Python is running; LinkingModule buffers it until the bridge
|
|
24
|
+
// callback is registered so Linking.get_initial_url() sees it.
|
|
23
25
|
for context in connectionOptions.urlContexts {
|
|
24
|
-
|
|
26
|
+
LinkingModule.deliver(url: context.url.absoluteString)
|
|
25
27
|
}
|
|
26
28
|
let window = UIWindow(windowScene: windowScene)
|
|
27
29
|
let root = ViewController()
|
|
@@ -33,33 +35,26 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|
|
33
35
|
|
|
34
36
|
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
|
|
35
37
|
for context in URLContexts {
|
|
36
|
-
|
|
38
|
+
LinkingModule.deliver(url: context.url.absoluteString)
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
// MARK: - AppState forwarding
|
|
41
43
|
|
|
42
|
-
private func dispatchAppState(_ state: String) {
|
|
43
|
-
guard PythonRuntime.shared.started else { return }
|
|
44
|
-
PythonRuntime.shared.notify(
|
|
45
|
-
module: "pythonnative.native_modules.app_state", function: "dispatch_app_state", state
|
|
46
|
-
)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
44
|
func sceneDidBecomeActive(_ scene: UIScene) {
|
|
50
|
-
|
|
45
|
+
AppStateModule.dispatch("active")
|
|
51
46
|
}
|
|
52
47
|
|
|
53
48
|
func sceneWillResignActive(_ scene: UIScene) {
|
|
54
|
-
|
|
49
|
+
AppStateModule.dispatch("inactive")
|
|
55
50
|
}
|
|
56
51
|
|
|
57
52
|
func sceneWillEnterForeground(_ scene: UIScene) {
|
|
58
|
-
|
|
53
|
+
AppStateModule.dispatch("inactive")
|
|
59
54
|
}
|
|
60
55
|
|
|
61
56
|
func sceneDidEnterBackground(_ scene: UIScene) {
|
|
62
|
-
|
|
57
|
+
AppStateModule.dispatch("background")
|
|
63
58
|
}
|
|
64
59
|
|
|
65
60
|
func sceneDidDisconnect(_ scene: UIScene) {}
|
|
@@ -2,158 +2,28 @@
|
|
|
2
2
|
// ViewController.swift
|
|
3
3
|
// ios_template
|
|
4
4
|
//
|
|
5
|
-
// Hosts one PythonNative screen.
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// Hosts one PythonNative screen. All screen plumbing lives in
|
|
6
|
+
// PythonNativeKit's PNViewController; this subclass only starts the
|
|
7
|
+
// embedded interpreter (showing a bootstrap error on failure) and
|
|
8
|
+
// points dev builds at the hot-reload overlay.
|
|
8
9
|
//
|
|
9
10
|
|
|
11
|
+
import PythonNativeKit
|
|
10
12
|
import UIKit
|
|
11
13
|
|
|
12
|
-
class ViewController:
|
|
13
|
-
|
|
14
|
-
// these before presenting the controller; nil means the app's entry
|
|
15
|
-
// module (Info.plist `PNEntryModule`, default "app.main").
|
|
16
|
-
@objc dynamic var requestedScreenPath: String? = nil
|
|
17
|
-
@objc dynamic var requestedScreenArgsJSON: String? = nil
|
|
18
|
-
|
|
19
|
-
private var screen: PyRef? = nil
|
|
20
|
-
#if DEBUG
|
|
21
|
-
private var hotReloadTimer: Timer? = nil
|
|
22
|
-
#endif
|
|
23
|
-
|
|
24
|
-
override func viewDidLoad() {
|
|
25
|
-
super.viewDidLoad()
|
|
26
|
-
view.backgroundColor = .systemBackground
|
|
27
|
-
|
|
14
|
+
final class ViewController: PNViewController {
|
|
15
|
+
override func prepareRuntime() -> Bool {
|
|
28
16
|
do {
|
|
29
17
|
try PythonRuntime.shared.ensureStarted()
|
|
30
18
|
} catch {
|
|
31
19
|
showBootstrapError("Python failed to start.\n\n\(error)")
|
|
32
|
-
return
|
|
20
|
+
return false
|
|
33
21
|
}
|
|
34
|
-
|
|
35
|
-
// PythonNative's convention is "import the module and grab its
|
|
36
|
-
// top-level `App` attribute". The entry module comes from
|
|
37
|
-
// `app.entry_point` in pythonnative.toml (written to Info.plist as
|
|
38
|
-
// `PNEntryModule` by `pn build`); push navigation overrides it via
|
|
39
|
-
// `requestedScreenPath`.
|
|
40
|
-
let entryModule = Bundle.main.object(forInfoDictionaryKey: "PNEntryModule") as? String
|
|
41
|
-
let screenPath: String = requestedScreenPath ?? entryModule ?? "app.main"
|
|
42
|
-
let addr = UInt(bitPattern: Unmanaged.passUnretained(self).toOpaque())
|
|
43
|
-
do {
|
|
44
|
-
let screen: PyRef
|
|
45
|
-
if let argsJSON = requestedScreenArgsJSON {
|
|
46
|
-
screen = try PythonRuntime.shared.call(
|
|
47
|
-
module: "pythonnative.hosts", function: "create_screen", screenPath, addr, argsJSON
|
|
48
|
-
)
|
|
49
|
-
} else {
|
|
50
|
-
screen = try PythonRuntime.shared.call(
|
|
51
|
-
module: "pythonnative.hosts", function: "create_screen", screenPath, addr
|
|
52
|
-
)
|
|
53
|
-
}
|
|
54
|
-
self.screen = screen
|
|
55
|
-
#if DEBUG
|
|
56
|
-
let devRoot = "\(NSHomeDirectory())/Documents/pythonnative_dev"
|
|
57
|
-
try screen.call("enable_hot_reload", "\(devRoot)/reload.json", devRoot)
|
|
58
|
-
#endif
|
|
59
|
-
try screen.call("on_create")
|
|
60
|
-
#if DEBUG
|
|
61
|
-
startHotReloadPolling()
|
|
62
|
-
#endif
|
|
63
|
-
} catch {
|
|
64
|
-
showBootstrapError("The PythonNative screen failed to mount.\n\n\(error)")
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// MARK: - Lifecycle forwarding
|
|
69
|
-
|
|
70
|
-
private func forwardLifecycle(_ event: String) {
|
|
71
|
-
guard screen != nil else { return }
|
|
72
|
-
let addr = UInt(bitPattern: Unmanaged.passUnretained(self).toOpaque())
|
|
73
|
-
PythonRuntime.shared.notify(
|
|
74
|
-
module: "pythonnative.hosts", function: "forward_lifecycle", addr, event
|
|
75
|
-
)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
override func viewWillAppear(_ animated: Bool) {
|
|
79
|
-
super.viewWillAppear(animated)
|
|
80
|
-
forwardLifecycle("on_start")
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
override func viewDidLayoutSubviews() {
|
|
84
|
-
super.viewDidLayoutSubviews()
|
|
85
|
-
// The root view's safeAreaInsets are only valid after iOS has
|
|
86
|
-
// positioned the view in its window; forward every layout pass
|
|
87
|
-
// to Python so the reconciler can re-run layout against the
|
|
88
|
-
// correct viewport (initial mount, rotation, multitasking, etc.).
|
|
89
|
-
forwardLifecycle("on_layout")
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
override func viewDidAppear(_ animated: Bool) {
|
|
93
|
-
super.viewDidAppear(animated)
|
|
94
|
-
forwardLifecycle("on_resume")
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
override func viewWillDisappear(_ animated: Bool) {
|
|
98
|
-
super.viewWillDisappear(animated)
|
|
99
|
-
forwardLifecycle("on_pause")
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
override func viewDidDisappear(_ animated: Bool) {
|
|
103
|
-
super.viewDidDisappear(animated)
|
|
104
|
-
forwardLifecycle("on_stop")
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
override func encodeRestorableState(with coder: NSCoder) {
|
|
108
|
-
super.encodeRestorableState(with: coder)
|
|
109
|
-
forwardLifecycle("on_save_instance_state")
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
override func decodeRestorableState(with coder: NSCoder) {
|
|
113
|
-
super.decodeRestorableState(with: coder)
|
|
114
|
-
forwardLifecycle("on_restore_instance_state")
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
deinit {
|
|
118
22
|
#if DEBUG
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if screen != nil {
|
|
122
|
-
let addr = UInt(bitPattern: Unmanaged.passUnretained(self).toOpaque())
|
|
123
|
-
PythonRuntime.shared.notify(
|
|
124
|
-
module: "pythonnative.hosts", function: "forward_lifecycle", addr, "on_destroy"
|
|
125
|
-
)
|
|
23
|
+
if devRoot == nil {
|
|
24
|
+
devRoot = "\(NSHomeDirectory())/Documents/pythonnative_dev"
|
|
126
25
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
// MARK: - Hot reload (dev builds only)
|
|
130
|
-
|
|
131
|
-
#if DEBUG
|
|
132
|
-
private func startHotReloadPolling() {
|
|
133
|
-
hotReloadTimer?.invalidate()
|
|
134
|
-
hotReloadTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] _ in
|
|
135
|
-
guard let screen = self?.screen else { return }
|
|
136
|
-
do {
|
|
137
|
-
try screen.call("hot_reload_tick")
|
|
138
|
-
} catch {
|
|
139
|
-
NSLog("[PN] hot_reload_tick failed: \(error)")
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
#endif
|
|
144
|
-
|
|
145
|
-
// MARK: - Bootstrap error UI
|
|
146
|
-
|
|
147
|
-
private func showBootstrapError(_ message: String) {
|
|
148
|
-
NSLog("[PN] %@", message)
|
|
149
|
-
let text = UITextView(frame: view.bounds)
|
|
150
|
-
text.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
151
|
-
text.isEditable = false
|
|
152
|
-
text.backgroundColor = UIColor(red: 0.75, green: 0.10, blue: 0.10, alpha: 1.0)
|
|
153
|
-
text.textColor = .white
|
|
154
|
-
text.font = UIFont.monospacedSystemFont(ofSize: 13, weight: .regular)
|
|
155
|
-
text.textContainerInset = UIEdgeInsets(top: 60, left: 16, bottom: 40, right: 16)
|
|
156
|
-
text.text = "PythonNative could not start\n\n\(message)"
|
|
157
|
-
view.addSubview(text)
|
|
26
|
+
#endif
|
|
27
|
+
return true
|
|
158
28
|
}
|
|
159
29
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
archiveVersion = 1;
|
|
4
4
|
classes = {
|
|
5
5
|
};
|
|
6
|
-
objectVersion =
|
|
6
|
+
objectVersion = 60;
|
|
7
7
|
objects = {
|
|
8
8
|
|
|
9
9
|
/* Begin PBXBuildFile section */
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
AA0000000000000000000005 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = AA0000000000000000000003 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
|
21
21
|
AA0000000000000000000007 /* app in Resources */ = {isa = PBXBuildFile; fileRef = AA0000000000000000000006 /* app */; };
|
|
22
22
|
AA0000000000000000000009 /* app_packages in Resources */ = {isa = PBXBuildFile; fileRef = AA0000000000000000000008 /* app_packages */; };
|
|
23
|
+
AA000000000000000000000D /* PythonNativeKit in Frameworks */ = {isa = PBXBuildFile; productRef = AA000000000000000000000C /* PythonNativeKit */; };
|
|
23
24
|
/* End PBXBuildFile section */
|
|
24
25
|
|
|
25
26
|
/* Begin PBXContainerItemProxy section */
|
|
@@ -65,6 +66,7 @@
|
|
|
65
66
|
buildActionMask = 2147483647;
|
|
66
67
|
files = (
|
|
67
68
|
AA0000000000000000000004 /* Python.xcframework in Frameworks */,
|
|
69
|
+
AA000000000000000000000D /* PythonNativeKit in Frameworks */,
|
|
68
70
|
);
|
|
69
71
|
runOnlyForDeploymentPostprocessing = 0;
|
|
70
72
|
};
|
|
@@ -158,6 +160,9 @@
|
|
|
158
160
|
dependencies = (
|
|
159
161
|
);
|
|
160
162
|
name = ios_template;
|
|
163
|
+
packageProductDependencies = (
|
|
164
|
+
AA000000000000000000000C /* PythonNativeKit */,
|
|
165
|
+
);
|
|
161
166
|
productName = ios_template;
|
|
162
167
|
productReference = 146EBB7E2A40CCC900E4A2B3 /* ios_template.app */;
|
|
163
168
|
productType = "com.apple.product-type.application";
|
|
@@ -230,6 +235,9 @@
|
|
|
230
235
|
Base,
|
|
231
236
|
);
|
|
232
237
|
mainGroup = 146EBB752A40CCC900E4A2B3;
|
|
238
|
+
packageReferences = (
|
|
239
|
+
AA000000000000000000000E /* XCLocalSwiftPackageReference "PythonNativeKit" */,
|
|
240
|
+
);
|
|
233
241
|
productRefGroup = 146EBB7F2A40CCC900E4A2B3 /* Products */;
|
|
234
242
|
projectDirPath = "";
|
|
235
243
|
projectRoot = "";
|
|
@@ -504,6 +512,18 @@
|
|
|
504
512
|
"@executable_path/Frameworks",
|
|
505
513
|
);
|
|
506
514
|
MARKETING_VERSION = 1.0;
|
|
515
|
+
OTHER_LDFLAGS = (
|
|
516
|
+
"$(inherited)",
|
|
517
|
+
"-Wl,-exported_symbol,_pn_bridge_apply",
|
|
518
|
+
"-Wl,-exported_symbol,_pn_bridge_measure",
|
|
519
|
+
"-Wl,-exported_symbol,_pn_bridge_command",
|
|
520
|
+
"-Wl,-exported_symbol,_pn_bridge_animate",
|
|
521
|
+
"-Wl,-exported_symbol,_pn_bridge_call",
|
|
522
|
+
"-Wl,-exported_symbol,_pn_bridge_free",
|
|
523
|
+
"-Wl,-exported_symbol,_pn_bridge_set_callback",
|
|
524
|
+
"-Wl,-exported_symbol,_pn_bridge_protocol_version",
|
|
525
|
+
"-Wl,-exported_symbol,_main",
|
|
526
|
+
);
|
|
507
527
|
PRODUCT_BUNDLE_IDENTIFIER = "com.pythonnative.ios-template";
|
|
508
528
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
509
529
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
|
@@ -542,6 +562,18 @@
|
|
|
542
562
|
"@executable_path/Frameworks",
|
|
543
563
|
);
|
|
544
564
|
MARKETING_VERSION = 1.0;
|
|
565
|
+
OTHER_LDFLAGS = (
|
|
566
|
+
"$(inherited)",
|
|
567
|
+
"-Wl,-exported_symbol,_pn_bridge_apply",
|
|
568
|
+
"-Wl,-exported_symbol,_pn_bridge_measure",
|
|
569
|
+
"-Wl,-exported_symbol,_pn_bridge_command",
|
|
570
|
+
"-Wl,-exported_symbol,_pn_bridge_animate",
|
|
571
|
+
"-Wl,-exported_symbol,_pn_bridge_call",
|
|
572
|
+
"-Wl,-exported_symbol,_pn_bridge_free",
|
|
573
|
+
"-Wl,-exported_symbol,_pn_bridge_set_callback",
|
|
574
|
+
"-Wl,-exported_symbol,_pn_bridge_protocol_version",
|
|
575
|
+
"-Wl,-exported_symbol,_main",
|
|
576
|
+
);
|
|
545
577
|
PRODUCT_BUNDLE_IDENTIFIER = "com.pythonnative.ios-template";
|
|
546
578
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
547
579
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
|
@@ -668,6 +700,20 @@
|
|
|
668
700
|
defaultConfigurationName = Release;
|
|
669
701
|
};
|
|
670
702
|
/* End XCConfigurationList section */
|
|
703
|
+
|
|
704
|
+
/* Begin XCLocalSwiftPackageReference section */
|
|
705
|
+
AA000000000000000000000E /* XCLocalSwiftPackageReference "PythonNativeKit" */ = {
|
|
706
|
+
isa = XCLocalSwiftPackageReference;
|
|
707
|
+
relativePath = PythonNativeKit;
|
|
708
|
+
};
|
|
709
|
+
/* End XCLocalSwiftPackageReference section */
|
|
710
|
+
|
|
711
|
+
/* Begin XCSwiftPackageProductDependency section */
|
|
712
|
+
AA000000000000000000000C /* PythonNativeKit */ = {
|
|
713
|
+
isa = XCSwiftPackageProductDependency;
|
|
714
|
+
productName = PythonNativeKit;
|
|
715
|
+
};
|
|
716
|
+
/* End XCSwiftPackageProductDependency section */
|
|
671
717
|
};
|
|
672
718
|
rootObject = 146EBB762A40CCC900E4A2B3 /* Project object */;
|
|
673
719
|
}
|
pythonnative/utils.py
CHANGED
|
@@ -6,19 +6,13 @@ results are cached the first time
|
|
|
6
6
|
[`IS_ANDROID`][pythonnative.utils.IS_ANDROID] and
|
|
7
7
|
[`IS_IOS`][pythonnative.utils.IS_IOS] are read.
|
|
8
8
|
|
|
9
|
-
The Android variants also expose a small global registry for the
|
|
10
|
-
current `Activity` / `Context` and the screen's fragment container view;
|
|
11
|
-
both are populated by the bundled Android template before any
|
|
12
|
-
PythonNative code runs.
|
|
13
|
-
|
|
14
9
|
Attributes:
|
|
15
10
|
IS_ANDROID: `True` when running inside an Android process (either
|
|
16
11
|
because `ANDROID_*` env vars are present or because Chaquopy's
|
|
17
12
|
`java` module imports successfully).
|
|
18
13
|
IS_IOS: `True` when running inside an iOS app bundle (signaled by
|
|
19
14
|
`PN_PLATFORM=ios`, `sys.platform == "ios"`, or a Simulator
|
|
20
|
-
`HOME` path).
|
|
21
|
-
not enough to trigger this flag.
|
|
15
|
+
`HOME` path).
|
|
22
16
|
IS_DESKTOP: `True` when running the desktop preview backend
|
|
23
17
|
(signaled by `PN_PLATFORM=desktop`, set by ``pn preview``).
|
|
24
18
|
This drives the Tkinter native-view registry so a PythonNative
|
|
@@ -27,7 +21,7 @@ Attributes:
|
|
|
27
21
|
|
|
28
22
|
import os
|
|
29
23
|
import sys
|
|
30
|
-
from typing import
|
|
24
|
+
from typing import Optional
|
|
31
25
|
|
|
32
26
|
# ======================================================================
|
|
33
27
|
# Platform detection
|
|
@@ -65,10 +59,8 @@ def _detect_ios() -> bool:
|
|
|
65
59
|
- `/CoreSimulator/Devices/` in `$HOME` (iOS Simulator fallback if
|
|
66
60
|
the template signal is missing for some reason).
|
|
67
61
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
and treating that as iOS would cause subtle side effects
|
|
71
|
-
(e.g., stdout redirection) on desktop machines.
|
|
62
|
+
Running on macOS is never enough on its own: the desktop preview
|
|
63
|
+
and unit tests run there and must not be mistaken for iOS.
|
|
72
64
|
"""
|
|
73
65
|
if os.environ.get("PN_PLATFORM") == "ios":
|
|
74
66
|
return True
|
|
@@ -148,73 +140,3 @@ Set by ``pn preview`` via ``PN_PLATFORM=desktop``. Mutually exclusive
|
|
|
148
140
|
with `IS_ANDROID` / `IS_IOS`. Off-device unit tests leave this
|
|
149
141
|
``False`` and inject a mock registry instead.
|
|
150
142
|
"""
|
|
151
|
-
|
|
152
|
-
# ======================================================================
|
|
153
|
-
# Android context management
|
|
154
|
-
# ======================================================================
|
|
155
|
-
|
|
156
|
-
_android_context: Any = None
|
|
157
|
-
_android_fragment_container: Any = None
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def set_android_context(context: Any) -> None:
|
|
161
|
-
"""Record the current Android `Activity`/`Context`.
|
|
162
|
-
|
|
163
|
-
Called by the bundled Android template before any view is created;
|
|
164
|
-
most app code should not call this directly.
|
|
165
|
-
|
|
166
|
-
Args:
|
|
167
|
-
context: A Java `android.content.Context` (typically the
|
|
168
|
-
current `Activity`).
|
|
169
|
-
"""
|
|
170
|
-
global _android_context
|
|
171
|
-
_android_context = context
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
def set_android_fragment_container(container_view: Any) -> None:
|
|
175
|
-
"""Record the current `Fragment` root container `ViewGroup`.
|
|
176
|
-
|
|
177
|
-
Args:
|
|
178
|
-
container_view: A Java `android.view.ViewGroup` that holds the
|
|
179
|
-
screen's view tree.
|
|
180
|
-
"""
|
|
181
|
-
global _android_fragment_container
|
|
182
|
-
_android_fragment_container = container_view
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
def get_android_context() -> Any:
|
|
186
|
-
"""Return the current Android `Activity`/`Context`.
|
|
187
|
-
|
|
188
|
-
Returns:
|
|
189
|
-
The most recently recorded Android context.
|
|
190
|
-
|
|
191
|
-
Raises:
|
|
192
|
-
RuntimeError: If called on a non-Android platform, or before
|
|
193
|
-
the template has registered a context.
|
|
194
|
-
"""
|
|
195
|
-
if not IS_ANDROID:
|
|
196
|
-
raise RuntimeError("get_android_context() called on non-Android platform")
|
|
197
|
-
if _android_context is None:
|
|
198
|
-
raise RuntimeError(
|
|
199
|
-
"Android context not set. Ensure the screen host is initialized from an Activity before constructing views."
|
|
200
|
-
)
|
|
201
|
-
return _android_context
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
def get_android_fragment_container() -> Any:
|
|
205
|
-
"""Return the current `Fragment` container `ViewGroup`.
|
|
206
|
-
|
|
207
|
-
Returns:
|
|
208
|
-
The most recently recorded fragment container view.
|
|
209
|
-
|
|
210
|
-
Raises:
|
|
211
|
-
RuntimeError: If called on a non-Android platform, or before
|
|
212
|
-
`ScreenFragment` has registered its container.
|
|
213
|
-
"""
|
|
214
|
-
if not IS_ANDROID:
|
|
215
|
-
raise RuntimeError("get_android_fragment_container() called on non-Android platform")
|
|
216
|
-
if _android_fragment_container is None:
|
|
217
|
-
raise RuntimeError(
|
|
218
|
-
"Android fragment container not set. Ensure ScreenFragment has been created before set_root_view."
|
|
219
|
-
)
|
|
220
|
-
return _android_fragment_container
|
pythonnative/virtual_rows.py
CHANGED
|
@@ -90,9 +90,9 @@ class RowSubtree:
|
|
|
90
90
|
class RowHostPool:
|
|
91
91
|
"""Per-list bookkeeping of row subtrees keyed by native container.
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
are
|
|
95
|
-
|
|
93
|
+
The view backend creates one pool per virtualized list view. Keys
|
|
94
|
+
are the container ids native sends with ``on_bind_row`` (stable for
|
|
95
|
+
the life of a recycled cell).
|
|
96
96
|
"""
|
|
97
97
|
|
|
98
98
|
def __init__(self) -> None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythonnative
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.37.0
|
|
4
4
|
Summary: Cross-platform native UI toolkit for Android and iOS
|
|
5
5
|
Author: Owen Carey
|
|
6
6
|
License: MIT License
|
|
@@ -44,8 +44,6 @@ License-File: LICENSE
|
|
|
44
44
|
Requires-Dist: requests>=2.31.0
|
|
45
45
|
Requires-Dist: tomli>=2.0; python_version < "3.11"
|
|
46
46
|
Requires-Dist: typing_extensions>=4.4; python_version < "3.11"
|
|
47
|
-
Provides-Extra: ios
|
|
48
|
-
Requires-Dist: rubicon-objc<0.5.0,>=0.4.6; extra == "ios"
|
|
49
47
|
Provides-Extra: build
|
|
50
48
|
Requires-Dist: Pillow>=10.0; extra == "build"
|
|
51
49
|
Dynamic: license-file
|
|
@@ -78,7 +76,7 @@ Dynamic: license-file
|
|
|
78
76
|
|
|
79
77
|
## Overview
|
|
80
78
|
|
|
81
|
-
PythonNative is a cross-platform toolkit for building native Android and iOS apps in Python. It provides a **declarative, React-like component model** with hooks and automatic reconciliation
|
|
79
|
+
PythonNative is a cross-platform toolkit for building native Android and iOS apps in Python. It provides a **declarative, React-like component model** with hooks and automatic reconciliation on top of a **native rendering core** written in Swift and Kotlin. Write function components with `use_state`, `use_effect`, and friends, just like React, and let PythonNative handle creating and updating native views.
|
|
82
80
|
|
|
83
81
|
## Features
|
|
84
82
|
|
|
@@ -94,7 +92,7 @@ PythonNative is a cross-platform toolkit for building native Android and iOS app
|
|
|
94
92
|
- **Native-driven animations:** The `Animated` API (timing / spring / decay / loop / stagger, awaitable or fire-and-forget) hands animations to Core Animation and `ViewPropertyAnimator` whenever possible, so no Python code runs per frame; a pure-Python ticker covers the rest. `interpolate`, arithmetic operators on animated nodes, `Animated.event` scroll binding, and `diff_clamp` cover the scroll-driven patterns (collapsing headers, parallax) that define native feel.
|
|
95
93
|
- **Native gesture system:** Attach `Tap`, `LongPress`, `Pan`, `Swipe`, `Fling`, `Pinch`, and `Rotation` recognizers to any view via the `gestures=` prop, backed by `UIGestureRecognizer` on iOS and a unit-testable pure-Python arbiter on Android and desktop. Compose them with `Race`, `Exclusive`, and `Simultaneous` for cross-gesture arbitration (single vs. double tap, drag vs. long press).
|
|
96
94
|
- **Virtualized lists:** `FlatList` / `SectionList` window their rows in Python over the platform scroll view: uniform, exact, or measured variable heights, grids, headers/footers, infinite scroll, and an imperative scroll controller, identical on every platform.
|
|
97
|
-
- **
|
|
95
|
+
- **Native rendering core:** each commit is one serialized transaction applied by Swift and Kotlin component managers, the same shape as React Native's Fabric. Device APIs are native modules registered by name (TurboModules-style), callable from thin Python facades.
|
|
98
96
|
- **Custom-component SDK:** Wrap any platform widget as a first-class element with type-checked props via `pythonnative.sdk` (`Props`, `@native_component`, `element_factory`). Plugins distributed on PyPI auto-register through the `pythonnative.handlers` entry-point group.
|
|
99
97
|
- **CLI scaffolding:** `pn init` creates a ready-to-run project; `pn run android` and `pn run ios` build and launch your app.
|
|
100
98
|
- **Instant desktop preview:** `pn preview` renders your app in a native desktop window via Tkinter with Fast Refresh on every save: iterate on layout, state, and navigation in milliseconds without booting a simulator or device. The reconciler, hooks, layout engine, and navigation are the same code that ships to the phone.
|