pythonnative 0.36.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.
Files changed (174) hide show
  1. pythonnative/__init__.py +6 -4
  2. pythonnative/_ios_log.py +1 -1
  3. pythonnative/alerts.py +34 -88
  4. pythonnative/bootstrap.py +90 -0
  5. pythonnative/bridge/__init__.py +306 -0
  6. pythonnative/bridge/android.py +78 -0
  7. pythonnative/bridge/codec.py +164 -0
  8. pythonnative/bridge/fake.py +236 -0
  9. pythonnative/bridge/ios.py +138 -0
  10. pythonnative/components/layout.py +65 -3
  11. pythonnative/gestures.py +13 -10
  12. pythonnative/hooks.py +41 -26
  13. pythonnative/hosts/__init__.py +13 -36
  14. pythonnative/hosts/native.py +348 -0
  15. pythonnative/images.py +6 -5
  16. pythonnative/native_modules/__init__.py +30 -29
  17. pythonnative/native_modules/app_state.py +10 -6
  18. pythonnative/native_modules/battery.py +20 -84
  19. pythonnative/native_modules/biometrics.py +11 -124
  20. pythonnative/native_modules/camera.py +15 -291
  21. pythonnative/native_modules/clipboard.py +14 -109
  22. pythonnative/native_modules/desktop.py +322 -0
  23. pythonnative/native_modules/file_system.py +15 -21
  24. pythonnative/native_modules/haptics.py +19 -129
  25. pythonnative/native_modules/linking.py +24 -137
  26. pythonnative/native_modules/location.py +28 -180
  27. pythonnative/native_modules/net_info.py +25 -135
  28. pythonnative/native_modules/notifications.py +36 -257
  29. pythonnative/native_modules/permissions.py +20 -184
  30. pythonnative/native_modules/registry.py +513 -0
  31. pythonnative/native_modules/secure_store.py +18 -155
  32. pythonnative/native_modules/share.py +8 -113
  33. pythonnative/native_views/__init__.py +67 -86
  34. pythonnative/native_views/bridge_backend.py +264 -0
  35. pythonnative/platform.py +19 -15
  36. pythonnative/platform_metrics.py +3 -3
  37. pythonnative/project/android.py +0 -3
  38. pythonnative/project/builder.py +38 -16
  39. pythonnative/project/config.py +12 -2
  40. pythonnative/project/plugins.py +366 -0
  41. pythonnative/py.typed +0 -0
  42. pythonnative/runtime.py +35 -203
  43. pythonnative/sdk/__init__.py +48 -33
  44. pythonnative/sdk/_components.py +57 -76
  45. pythonnative/storage.py +18 -243
  46. pythonnative/templates/android_template/app/build.gradle +6 -9
  47. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +34 -166
  48. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +13 -151
  49. pythonnative/templates/android_template/pythonnative/build.gradle +48 -0
  50. pythonnative/templates/android_template/pythonnative/src/main/AndroidManifest.xml +11 -0
  51. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PNBridge.kt +214 -0
  52. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PythonHost.kt +20 -0
  53. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/AnimationSpecs.kt +141 -0
  54. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/PNAnimator.kt +249 -0
  55. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/JsonUtil.kt +175 -0
  56. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/MainThread.kt +32 -0
  57. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNLog.kt +33 -0
  58. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNRegistry.kt +82 -0
  59. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNTransaction.kt +80 -0
  60. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/TransactionApplier.kt +115 -0
  61. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/ViewRegistry.kt +74 -0
  62. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/BuiltinComponents.kt +35 -0
  63. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ButtonManager.kt +33 -0
  64. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ComponentManager.kt +213 -0
  65. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ContainerManagers.kt +69 -0
  66. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ControlManagers.kt +186 -0
  67. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/DatePickerManager.kt +123 -0
  68. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageLoader.kt +141 -0
  69. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageManager.kt +156 -0
  70. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ModalManager.kt +134 -0
  71. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PNColor.kt +201 -0
  72. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PickerManager.kt +87 -0
  73. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PortalManager.kt +55 -0
  74. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PressableManager.kt +122 -0
  75. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ScrollViewManager.kt +214 -0
  76. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/SegmentedControlManager.kt +113 -0
  77. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/StatusBarManager.kt +52 -0
  78. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TabBarManager.kt +131 -0
  79. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextInputManager.kt +245 -0
  80. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextManager.kt +233 -0
  81. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ViewStyler.kt +355 -0
  82. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/VirtualListManager.kt +179 -0
  83. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/WebViewManager.kt +149 -0
  84. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureArbiter.kt +233 -0
  85. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureCoordinator.kt +127 -0
  86. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureRecognizers.kt +527 -0
  87. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/BuiltinModules.kt +73 -0
  88. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/DeviceModules.kt +316 -0
  89. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/HostModule.kt +97 -0
  90. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/MediaModules.kt +200 -0
  91. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NativeModule.kt +135 -0
  92. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NotificationsModule.kt +100 -0
  93. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/PermissionsModule.kt +88 -0
  94. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/StorageModules.kt +89 -0
  95. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/SystemModules.kt +178 -0
  96. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/plugins/GeneratedPlugins.kt +16 -0
  97. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/Navigator.kt +116 -0
  98. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/PNScreenFragment.kt +223 -0
  99. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/ScreenRegistry.kt +80 -0
  100. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNAccessibilityDelegate.kt +5 -4
  101. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNBorderDrawable.kt +12 -3
  102. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/views/PNEditText.kt +18 -0
  103. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNFrameLayout.kt +9 -10
  104. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNVirtualListView.java +9 -12
  105. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/AnimationSpecsTest.kt +75 -0
  106. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/GestureArbiterTest.kt +197 -0
  107. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNColorTest.kt +47 -0
  108. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNTransactionTest.kt +78 -0
  109. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PromiseTest.kt +75 -0
  110. pythonnative/templates/android_template/settings.gradle +1 -0
  111. pythonnative/templates/ios_template/PythonNativeKit/Package.swift +21 -0
  112. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Animation/PNAnimator.swift +301 -0
  113. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNBridge.swift +191 -0
  114. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNRegistry.swift +140 -0
  115. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNTransaction.swift +165 -0
  116. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNViewRegistry.swift +53 -0
  117. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNButtonManagers.swift +206 -0
  118. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNColor.swift +173 -0
  119. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNComponentManager.swift +183 -0
  120. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNContainerView.swift +56 -0
  121. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNControlManagers.swift +281 -0
  122. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNImageManager.swift +221 -0
  123. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNOverlayManagers.swift +243 -0
  124. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNPressableManager.swift +114 -0
  125. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNScrollViewManager.swift +197 -0
  126. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTabBarManager.swift +106 -0
  127. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextInputManager.swift +289 -0
  128. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextManager.swift +230 -0
  129. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTransform.swift +82 -0
  130. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewManager.swift +86 -0
  131. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewState.swift +87 -0
  132. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewStyler.swift +340 -0
  133. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNVirtualListManager.swift +257 -0
  134. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNWebViewManager.swift +124 -0
  135. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Gestures/PNGestureCoordinator.swift +260 -0
  136. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/AlertModule.swift +92 -0
  137. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/HostModule.swift +158 -0
  138. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/LifecycleModules.swift +174 -0
  139. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/MediaModules.swift +177 -0
  140. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PNNativeModule.swift +125 -0
  141. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PermissionModules.swift +251 -0
  142. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/SystemModules.swift +251 -0
  143. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Plugins/PNPluginRegistration.swift +8 -0
  144. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNScreenRegistry.swift +46 -0
  145. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNViewController.swift +251 -0
  146. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNCompat.swift +46 -0
  147. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNJSON.swift +173 -0
  148. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNLog.swift +47 -0
  149. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNWindow.swift +51 -0
  150. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNColorTests.swift +50 -0
  151. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNGestureTests.swift +101 -0
  152. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNManagerTests.swift +124 -0
  153. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNModuleTests.swift +98 -0
  154. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNTransactionTests.swift +91 -0
  155. pythonnative/templates/ios_template/ios_template/AppDelegate.swift +8 -50
  156. pythonnative/templates/ios_template/ios_template/Info.plist +4 -4
  157. pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +38 -46
  158. pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +11 -16
  159. pythonnative/templates/ios_template/ios_template/ViewController.swift +12 -142
  160. pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +47 -1
  161. pythonnative/utils.py +4 -82
  162. pythonnative/virtual_rows.py +3 -3
  163. {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/METADATA +3 -5
  164. pythonnative-0.37.0.dist-info/RECORD +263 -0
  165. pythonnative/hosts/android.py +0 -269
  166. pythonnative/hosts/ios.py +0 -404
  167. pythonnative/native_views/android.py +0 -3702
  168. pythonnative/native_views/ios.py +0 -5253
  169. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -43
  170. pythonnative-0.36.0.dist-info/RECORD +0 -155
  171. {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/WHEEL +0 -0
  172. {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/entry_points.txt +0 -0
  173. {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/licenses/LICENSE +0 -0
  174. {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,183 @@
1
+ import UIKit
2
+
3
+ /// Base class for every element type's native implementation.
4
+ ///
5
+ /// One manager instance serves every view of its type; per-view state
6
+ /// is kept in `PNViewState`. Subclasses usually override `makeView` and
7
+ /// `apply`, and opt into `measure`, `command`, and the child hooks when
8
+ /// the stock behavior isn't right.
9
+ open class PNComponentManager {
10
+ public init() {}
11
+
12
+ // MARK: - Creation and props
13
+
14
+ /// Create the native view and apply its initial props.
15
+ open func createView(tag: Int64, props: [String: Any]) -> UIView {
16
+ let view = makeView(props: props)
17
+ view.translatesAutoresizingMaskIntoConstraints = true
18
+ let state = PNViewState.attach(view, tag: tag, typeName: String(describing: type(of: self)))
19
+ state.props = PNComponentManager.stripNulls(props)
20
+ apply(view: view, props: props, initial: true)
21
+ if let gestures = PNProps.value(props, "gestures") {
22
+ PNGestureCoordinator.shared.wire(view: view, specs: gestures)
23
+ }
24
+ return view
25
+ }
26
+
27
+ /// Hook run after the view is registered in `PNViewRegistry`.
28
+ open func didCreate(view: UIView, tag: Int64, props: [String: Any]) {}
29
+
30
+ /// Construct the bare native view. Subclasses override this.
31
+ open func makeView(props: [String: Any]) -> UIView {
32
+ PNContainerView(frame: .zero)
33
+ }
34
+
35
+ /// Merge `changed` into the view's props and apply them.
36
+ open func update(view: UIView, changed: [String: Any]) {
37
+ if let state = PNViewState.existing(for: view) {
38
+ for (key, value) in changed {
39
+ if value is NSNull {
40
+ state.props.removeValue(forKey: key)
41
+ } else {
42
+ state.props[key] = value
43
+ }
44
+ }
45
+ }
46
+ apply(view: view, props: changed, initial: false)
47
+ if PNProps.has(changed, "gestures") {
48
+ PNGestureCoordinator.shared.wire(view: view, specs: PNProps.value(changed, "gestures"))
49
+ }
50
+ }
51
+
52
+ /// Apply `props` (initial or changed) to the view. The default
53
+ /// applies every shared visual prop; leaf managers override.
54
+ open func apply(view: UIView, props: [String: Any], initial: Bool) {
55
+ PNViewStyler.applyCommon(view, props)
56
+ }
57
+
58
+ // MARK: - Children
59
+
60
+ /// Ensure `child` sits at `index` under `parent` (move-aware, clamped).
61
+ open func insertChild(parent: UIView, child: UIView, index: Int) {
62
+ child.translatesAutoresizingMaskIntoConstraints = true
63
+ let container = childContainer(for: parent)
64
+ let siblings = container.subviews
65
+ if child.superview === container, let current = siblings.firstIndex(where: { $0 === child }) {
66
+ let target = max(0, min(index, siblings.count - 1))
67
+ if current == target { return }
68
+ container.insertSubview(child, at: target)
69
+ return
70
+ }
71
+ let target = max(0, min(index, siblings.count))
72
+ container.insertSubview(child, at: target)
73
+ }
74
+
75
+ /// Detach `child` from `parent`.
76
+ open func removeChild(parent: UIView, child: UIView) {
77
+ if child.superview != nil {
78
+ child.removeFromSuperview()
79
+ }
80
+ }
81
+
82
+ /// The view children are inserted into (the view itself by default).
83
+ open func childContainer(for view: UIView) -> UIView {
84
+ view
85
+ }
86
+
87
+ // MARK: - Lifecycle
88
+
89
+ /// Release the view's resources and detach it from the hierarchy.
90
+ open func destroy(view: UIView) {
91
+ teardown(view: view)
92
+ PNGestureCoordinator.shared.unwire(view: view)
93
+ PNAnimator.shared.forget(view: view)
94
+ view.layer.removeAllAnimations()
95
+ if view.superview != nil {
96
+ view.removeFromSuperview()
97
+ }
98
+ }
99
+
100
+ /// Subclass hook for extra cleanup before the view is released.
101
+ open func teardown(view: UIView) {}
102
+
103
+ // MARK: - Layout
104
+
105
+ /// Apply a frame from the layout engine (transform-safe, non-finite values clamped).
106
+ open func setFrame(view: UIView, x: Double, y: Double, w: Double, h: Double) {
107
+ let fx = CGFloat(PNProps.finite(x))
108
+ let fy = CGFloat(PNProps.finite(y))
109
+ let fw = CGFloat(max(0, PNProps.finite(w)))
110
+ let fh = CGFloat(max(0, PNProps.finite(h)))
111
+ if !(x.isFinite && y.isFinite && w.isFinite && h.isFinite) {
112
+ PNLog.rateLimited(PNLog.components, key: "set_frame:nan", "[set_frame:nan] (\(x), \(y), \(w), \(h)) clamped")
113
+ }
114
+ view.translatesAutoresizingMaskIntoConstraints = true
115
+ // Setting `bounds.size` + `center` (instead of `frame`) keeps the
116
+ // frame meaningful under a non-identity transform and preserves
117
+ // a scroll view's content offset (its `bounds.origin`).
118
+ var bounds = view.bounds
119
+ bounds.size = CGSize(width: fw, height: fh)
120
+ view.bounds = bounds
121
+ let anchor = view.layer.anchorPoint
122
+ view.center = CGPoint(x: fx + fw * anchor.x, y: fy + fh * anchor.y)
123
+ PNViewStyler.syncFrameDependentStyles(view, size: CGSize(width: fw, height: fh))
124
+ if let scroll = view.superview as? UIScrollView, !(scroll is UITableView) {
125
+ let visible = scroll.bounds.size
126
+ let contentW = max(visible.width, fx + fw)
127
+ let contentH = max(visible.height, fy + fh)
128
+ if scroll.contentSize != CGSize(width: contentW, height: contentH) {
129
+ scroll.contentSize = CGSize(width: contentW, height: contentH)
130
+ }
131
+ }
132
+ }
133
+
134
+ /// Natural size under the constraints (either may be `1e6` / infinite).
135
+ open func measure(view: UIView, maxW: CGFloat, maxH: CGFloat) -> CGSize {
136
+ let mw = PNComponentManager.clampConstraint(maxW)
137
+ let mh = PNComponentManager.clampConstraint(maxH)
138
+ var size = view.sizeThatFits(CGSize(width: mw, height: mh))
139
+ if maxW.isFinite { size.width = min(size.width, maxW) }
140
+ if !size.width.isFinite { size.width = 0 }
141
+ if !size.height.isFinite { size.height = 0 }
142
+ return size
143
+ }
144
+
145
+ // MARK: - Commands and animation
146
+
147
+ /// Run an imperative command. Unknown commands return `nil`.
148
+ open func command(view: UIView, name: String, args: [String: Any]) -> Any? {
149
+ nil
150
+ }
151
+
152
+ /// Apply one Python-driven animation frame immediately.
153
+ open func setAnimatedProperty(view: UIView, prop: String, value: Any?) {
154
+ PNAnimator.shared.applyValue(view: view, prop: prop, value: value)
155
+ }
156
+
157
+ /// Start a natively driven animation. Returns `false` to fall back to Python.
158
+ open func startAnimation(view: UIView, id: Int64, prop: String, spec: [String: Any]) -> Bool {
159
+ PNAnimator.shared.start(view: view, id: id, prop: prop, spec: spec)
160
+ }
161
+
162
+ /// Cancel a native animation, returning the presentation value when known.
163
+ open func cancelAnimation(view: UIView, id: Int64) -> Any? {
164
+ PNAnimator.shared.cancel(id: id)
165
+ }
166
+
167
+ // MARK: - Helpers
168
+
169
+ /// The merged props recorded for `view` (empty when unmanaged).
170
+ public func mergedProps(_ view: UIView) -> [String: Any] {
171
+ PNViewState.existing(for: view)?.props ?? [:]
172
+ }
173
+
174
+ /// Clamp an infinite or oversized layout constraint for UIKit.
175
+ public static func clampConstraint(_ value: CGFloat, fallback: CGFloat = 10_000) -> CGFloat {
176
+ guard value.isFinite else { return fallback }
177
+ return max(0, min(value, fallback))
178
+ }
179
+
180
+ static func stripNulls(_ props: [String: Any]) -> [String: Any] {
181
+ props.filter { !($0.value is NSNull) }
182
+ }
183
+ }
@@ -0,0 +1,56 @@
1
+ import UIKit
2
+
3
+ /// Hit-test-aware container used by View, Row, Column, Pressable,
4
+ /// SafeAreaView, and KeyboardAvoidingView.
5
+ ///
6
+ /// Implements `pointer_events` (`"none"`, `"box_none"`, `"box_only"`,
7
+ /// anything else = auto) and `hit_slop` by overriding the two UIKit
8
+ /// hit-testing entry points. With neither prop set the behavior is
9
+ /// exactly stock `UIView`.
10
+ open class PNContainerView: UIView {
11
+ /// `nil` or `"auto"` means stock hit testing.
12
+ public var pointerEvents: String?
13
+ /// Extra touchable margin as (top, left, bottom, right).
14
+ public var hitSlop: UIEdgeInsets = .zero
15
+
16
+ public override init(frame: CGRect) {
17
+ super.init(frame: frame)
18
+ translatesAutoresizingMaskIntoConstraints = true
19
+ }
20
+
21
+ public required init?(coder: NSCoder) {
22
+ super.init(coder: coder)
23
+ translatesAutoresizingMaskIntoConstraints = true
24
+ }
25
+
26
+ open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
27
+ if pointerEvents == "none" { return false }
28
+ let expanded = bounds.inset(by: UIEdgeInsets(
29
+ top: -hitSlop.top, left: -hitSlop.left, bottom: -hitSlop.bottom, right: -hitSlop.right
30
+ ))
31
+ return expanded.contains(point)
32
+ }
33
+
34
+ open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
35
+ if pointerEvents == "none" { return nil }
36
+ let result = super.hitTest(point, with: event)
37
+ switch pointerEvents {
38
+ case "box_none":
39
+ return result === self ? nil : result
40
+ case "box_only":
41
+ return result == nil ? nil : self
42
+ default:
43
+ return result
44
+ }
45
+ }
46
+ }
47
+
48
+ /// Manager used for element types nobody registered. Creates a plain
49
+ /// `UIView` so the tree stays consistent.
50
+ final class PNPlaceholderManager: PNComponentManager {
51
+ override func createView(tag: Int64, props: [String: Any]) -> UIView {
52
+ let view = PNContainerView(frame: .zero)
53
+ view.backgroundColor = .clear
54
+ return view
55
+ }
56
+ }
@@ -0,0 +1,281 @@
1
+ import UIKit
2
+
3
+ /// `Switch`: `UISwitch` emitting `on_change(bool)`.
4
+ public final class PNSwitchManager: PNComponentManager {
5
+ public override func makeView(props: [String: Any]) -> UIView {
6
+ UISwitch(frame: .zero)
7
+ }
8
+
9
+ public override func createView(tag: Int64, props: [String: Any]) -> UIView {
10
+ let view = super.createView(tag: tag, props: props)
11
+ if let control = view as? UISwitch {
12
+ PNActionTarget.attach(control, events: .valueChanged) { [weak control] in
13
+ guard let control = control, PNViewState.existing(for: control)?.flag("suppress") != true else { return }
14
+ PNEvents.emit(control, "on_change", [control.isOn])
15
+ }
16
+ }
17
+ return view
18
+ }
19
+
20
+ public override func apply(view: UIView, props: [String: Any], initial: Bool) {
21
+ guard let control = view as? UISwitch, let state = PNViewState.existing(for: control) else { return }
22
+ if PNProps.has(props, "value") {
23
+ let value = PNProps.bool(PNProps.value(props, "value")) ?? false
24
+ if control.isOn != value {
25
+ state.extras["suppress"] = true
26
+ control.setOn(value, animated: !initial)
27
+ state.extras["suppress"] = false
28
+ }
29
+ }
30
+ if let color = PNColor.parse(PNProps.value(props, "on_tint_color") ?? PNProps.value(props, "tint_color")) {
31
+ control.onTintColor = color
32
+ }
33
+ if let color = PNColor.parse(PNProps.value(props, "thumb_color")) {
34
+ control.thumbTintColor = color
35
+ }
36
+ if PNProps.has(props, "enabled") {
37
+ control.isEnabled = PNProps.bool(PNProps.value(props, "enabled")) ?? true
38
+ }
39
+ PNViewStyler.applyAccessibility(control, props)
40
+ }
41
+
42
+ public override func measure(view: UIView, maxW: CGFloat, maxH: CGFloat) -> CGSize {
43
+ CGSize(width: 51, height: 31)
44
+ }
45
+
46
+ /// `UISwitch` ignores the size part of `frame`, but the base class sets
47
+ /// `bounds` directly, which would stretch its hit area (and accessibility
48
+ /// frame) across a stretched flex cross axis. Pin the box to the natural
49
+ /// size at the layout origin so taps land on the visible control.
50
+ public override func setFrame(view: UIView, x: Double, y: Double, w: Double, h: Double) {
51
+ let natural = view.intrinsicContentSize
52
+ let pw = natural.width > 0 ? min(w, Double(natural.width)) : w
53
+ let ph = natural.height > 0 ? min(h, Double(natural.height)) : h
54
+ super.setFrame(view: view, x: x, y: y, w: pw, h: ph)
55
+ }
56
+ }
57
+
58
+ /// `Slider`: `UISlider` emitting `on_change(float)`.
59
+ public final class PNSliderManager: PNComponentManager {
60
+ public override func makeView(props: [String: Any]) -> UIView {
61
+ UISlider(frame: .zero)
62
+ }
63
+
64
+ public override func createView(tag: Int64, props: [String: Any]) -> UIView {
65
+ let view = super.createView(tag: tag, props: props)
66
+ if let slider = view as? UISlider {
67
+ PNActionTarget.attach(slider, events: .valueChanged) { [weak slider] in
68
+ guard let slider = slider, PNViewState.existing(for: slider)?.flag("suppress") != true else { return }
69
+ PNEvents.emit(slider, "on_change", [Double(slider.value)])
70
+ }
71
+ PNActionTarget.attach(slider, events: [.touchUpInside, .touchUpOutside, .touchCancel]) { [weak slider] in
72
+ guard let slider = slider else { return }
73
+ PNEvents.emitIfWired(slider, "on_sliding_complete", [Double(slider.value)])
74
+ }
75
+ }
76
+ return view
77
+ }
78
+
79
+ public override func apply(view: UIView, props: [String: Any], initial: Bool) {
80
+ guard let slider = view as? UISlider, let state = PNViewState.existing(for: slider) else { return }
81
+ if let min = PNProps.double(PNProps.value(props, "min_value")) { slider.minimumValue = Float(min) }
82
+ if let max = PNProps.double(PNProps.value(props, "max_value")) { slider.maximumValue = Float(max) }
83
+ if let value = PNProps.double(PNProps.value(props, "value")), abs(Double(slider.value) - value) > 1e-9 {
84
+ state.extras["suppress"] = true
85
+ slider.setValue(Float(value), animated: !initial)
86
+ state.extras["suppress"] = false
87
+ }
88
+ if let color = PNColor.parse(PNProps.value(props, "minimum_track_color") ?? PNProps.value(props, "tint_color")) {
89
+ slider.minimumTrackTintColor = color
90
+ }
91
+ if let color = PNColor.parse(PNProps.value(props, "maximum_track_color")) { slider.maximumTrackTintColor = color }
92
+ if let color = PNColor.parse(PNProps.value(props, "thumb_color")) { slider.thumbTintColor = color }
93
+ if PNProps.has(props, "enabled") { slider.isEnabled = PNProps.bool(PNProps.value(props, "enabled")) ?? true }
94
+ PNViewStyler.applyAccessibility(slider, props)
95
+ }
96
+
97
+ public override func measure(view: UIView, maxW: CGFloat, maxH: CGFloat) -> CGSize {
98
+ let w = maxW.isFinite && maxW < 1e6 ? maxW : 200
99
+ return CGSize(width: max(w, 100), height: 34)
100
+ }
101
+ }
102
+
103
+ /// `ActivityIndicator`: `UIActivityIndicatorView`.
104
+ public final class PNActivityIndicatorManager: PNComponentManager {
105
+ public override func makeView(props: [String: Any]) -> UIView {
106
+ let style: UIActivityIndicatorView.Style = PNProps.string(PNProps.value(props, "size")) == "large" ? .large : .medium
107
+ let view = UIActivityIndicatorView(style: style)
108
+ view.hidesWhenStopped = true
109
+ return view
110
+ }
111
+
112
+ public override func apply(view: UIView, props: [String: Any], initial: Bool) {
113
+ guard let indicator = view as? UIActivityIndicatorView else { return }
114
+ if !initial, let size = PNProps.string(PNProps.value(props, "size")) {
115
+ indicator.style = size == "large" ? .large : .medium
116
+ }
117
+ if let color = PNColor.parse(PNProps.value(props, "color")) { indicator.color = color }
118
+ if PNProps.has(props, "animating") || initial {
119
+ let animating = PNProps.bool(PNProps.value(props, "animating")) ?? true
120
+ if animating { indicator.startAnimating() } else { indicator.stopAnimating() }
121
+ }
122
+ if PNProps.has(props, "hides_when_stopped") {
123
+ indicator.hidesWhenStopped = PNProps.bool(PNProps.value(props, "hides_when_stopped")) ?? true
124
+ }
125
+ PNViewStyler.applyAccessibility(indicator, props)
126
+ }
127
+
128
+ public override func measure(view: UIView, maxW: CGFloat, maxH: CGFloat) -> CGSize {
129
+ let size = view.intrinsicContentSize
130
+ return size.width > 0 ? size : CGSize(width: 20, height: 20)
131
+ }
132
+ }
133
+
134
+ /// `ProgressBar`: a determinate `UIProgressView`, or a spinning
135
+ /// `UIActivityIndicatorView` when `indeterminate` is set at creation.
136
+ public final class PNProgressBarManager: PNComponentManager {
137
+ public override func makeView(props: [String: Any]) -> UIView {
138
+ if PNProps.bool(PNProps.value(props, "indeterminate")) == true {
139
+ let spinner = UIActivityIndicatorView(style: .medium)
140
+ spinner.startAnimating()
141
+ return spinner
142
+ }
143
+ return UIProgressView(progressViewStyle: .default)
144
+ }
145
+
146
+ public override func apply(view: UIView, props: [String: Any], initial: Bool) {
147
+ if let spinner = view as? UIActivityIndicatorView {
148
+ if let color = PNColor.parse(PNProps.value(props, "color")) { spinner.color = color }
149
+ spinner.startAnimating()
150
+ } else if let bar = view as? UIProgressView {
151
+ if let value = PNProps.double(PNProps.value(props, "value") ?? PNProps.value(props, "progress")) {
152
+ bar.setProgress(Float(max(0, min(1, value))), animated: !initial)
153
+ }
154
+ if let color = PNColor.parse(PNProps.value(props, "color")) { bar.progressTintColor = color }
155
+ if let color = PNColor.parse(PNProps.value(props, "track_color")) { bar.trackTintColor = color }
156
+ }
157
+ PNViewStyler.applyAccessibility(view, props)
158
+ }
159
+
160
+ public override func measure(view: UIView, maxW: CGFloat, maxH: CGFloat) -> CGSize {
161
+ if view is UIActivityIndicatorView { return CGSize(width: 20, height: 20) }
162
+ let w = maxW.isFinite && maxW < 1e6 ? maxW : 200
163
+ return CGSize(width: max(w, 40), height: 4)
164
+ }
165
+ }
166
+
167
+ /// `SegmentedControl`: `UISegmentedControl` emitting `on_change(index)`.
168
+ public final class PNSegmentedControlManager: PNComponentManager {
169
+ public override func makeView(props: [String: Any]) -> UIView {
170
+ let segments = ((PNProps.value(props, "segments") as? [Any]) ?? []).map { PNProps.string($0) ?? "" }
171
+ return UISegmentedControl(items: segments)
172
+ }
173
+
174
+ public override func createView(tag: Int64, props: [String: Any]) -> UIView {
175
+ let view = super.createView(tag: tag, props: props)
176
+ if let control = view as? UISegmentedControl {
177
+ PNActionTarget.attach(control, events: .valueChanged) { [weak control] in
178
+ guard let control = control, PNViewState.existing(for: control)?.flag("suppress") != true else { return }
179
+ PNEvents.emit(control, "on_change", [control.selectedSegmentIndex])
180
+ }
181
+ }
182
+ return view
183
+ }
184
+
185
+ public override func apply(view: UIView, props: [String: Any], initial: Bool) {
186
+ guard let control = view as? UISegmentedControl, let state = PNViewState.existing(for: control) else { return }
187
+ let merged = state.props
188
+ var rebuilt = false
189
+ if let segments = PNProps.value(props, "segments") as? [Any] {
190
+ let titles = segments.map { PNProps.string($0) ?? "" }
191
+ if !initial, titles != (state.extras["segments"] as? [String]) {
192
+ state.extras["suppress"] = true
193
+ control.removeAllSegments()
194
+ for (i, title) in titles.enumerated() {
195
+ control.insertSegment(withTitle: title, at: i, animated: false)
196
+ }
197
+ state.extras["suppress"] = false
198
+ rebuilt = true
199
+ }
200
+ state.extras["segments"] = titles
201
+ }
202
+ if rebuilt || initial || PNProps.value(props, "selected_index") != nil {
203
+ state.extras["suppress"] = true
204
+ control.selectedSegmentIndex = PNProps.int(PNProps.value(merged, "selected_index")) ?? 0
205
+ state.extras["suppress"] = false
206
+ }
207
+ if let color = PNColor.parse(PNProps.value(props, "tint_color")) {
208
+ control.selectedSegmentTintColor = color
209
+ control.tintColor = color
210
+ }
211
+ if PNProps.has(props, "enabled") {
212
+ control.isEnabled = PNProps.bool(PNProps.value(props, "enabled")) ?? true
213
+ }
214
+ PNViewStyler.applyAccessibility(control, props)
215
+ }
216
+ }
217
+
218
+ /// `DatePicker`: compact `UIDatePicker` emitting ISO-style strings.
219
+ public final class PNDatePickerManager: PNComponentManager {
220
+ static let formats = ["date": "yyyy-MM-dd", "time": "HH:mm", "datetime": "yyyy-MM-dd'T'HH:mm"]
221
+ static var formatters: [String: DateFormatter] = [:]
222
+
223
+ static func formatter(mode: String) -> DateFormatter {
224
+ let format = formats[mode] ?? formats["date"]!
225
+ if let cached = formatters[format] { return cached }
226
+ let formatter = DateFormatter()
227
+ formatter.dateFormat = format
228
+ formatter.locale = Locale(identifier: "en_US_POSIX")
229
+ formatters[format] = formatter
230
+ return formatter
231
+ }
232
+
233
+ public override func makeView(props: [String: Any]) -> UIView {
234
+ let picker = UIDatePicker(frame: .zero)
235
+ picker.pnApplyCompactStyle()
236
+ return picker
237
+ }
238
+
239
+ public override func createView(tag: Int64, props: [String: Any]) -> UIView {
240
+ let view = super.createView(tag: tag, props: props)
241
+ if let picker = view as? UIDatePicker {
242
+ PNActionTarget.attach(picker, events: .valueChanged) { [weak picker] in
243
+ guard let picker = picker, let state = PNViewState.existing(for: picker), !state.flag("suppress") else { return }
244
+ let mode = PNProps.string(PNProps.value(state.props, "mode")) ?? "date"
245
+ PNEvents.emit(picker, "on_change", [PNDatePickerManager.formatter(mode: mode).string(from: picker.date)])
246
+ }
247
+ }
248
+ return view
249
+ }
250
+
251
+ public override func apply(view: UIView, props: [String: Any], initial: Bool) {
252
+ guard let picker = view as? UIDatePicker, let state = PNViewState.existing(for: picker) else { return }
253
+ let mode = PNProps.string(PNProps.value(state.props, "mode")) ?? "date"
254
+ if PNProps.value(props, "mode") != nil {
255
+ switch mode {
256
+ case "time": picker.datePickerMode = .time
257
+ case "datetime": picker.datePickerMode = .dateAndTime
258
+ default: picker.datePickerMode = .date
259
+ }
260
+ }
261
+ let formatter = PNDatePickerManager.formatter(mode: mode)
262
+ if PNProps.has(props, "minimum") {
263
+ picker.minimumDate = PNProps.string(PNProps.value(props, "minimum")).flatMap { formatter.date(from: $0) }
264
+ }
265
+ if PNProps.has(props, "maximum") {
266
+ picker.maximumDate = PNProps.string(PNProps.value(props, "maximum")).flatMap { formatter.date(from: $0) }
267
+ }
268
+ if let value = PNProps.string(PNProps.value(props, "value")), !value.isEmpty, let date = formatter.date(from: value) {
269
+ state.extras["suppress"] = true
270
+ picker.setDate(date, animated: false)
271
+ state.extras["suppress"] = false
272
+ }
273
+ if PNProps.has(props, "enabled") {
274
+ picker.isEnabled = PNProps.bool(PNProps.value(props, "enabled")) ?? true
275
+ }
276
+ if let color = PNColor.parse(PNProps.value(props, "tint_color")) {
277
+ picker.tintColor = color
278
+ }
279
+ PNViewStyler.applyAccessibility(picker, props)
280
+ }
281
+ }