pythonnative 0.26.0__py3-none-any.whl → 0.28.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 +1 -1
- pythonnative/animated.py +697 -62
- pythonnative/cli/pn.py +7 -0
- pythonnative/components.py +61 -7
- pythonnative/gestures.py +446 -34
- pythonnative/native_modules/net_info.py +73 -4
- pythonnative/native_modules/notifications.py +37 -9
- pythonnative/native_views/android.py +320 -16
- pythonnative/native_views/desktop.py +766 -47
- pythonnative/native_views/ios.py +522 -33
- pythonnative/reconciler.py +28 -0
- pythonnative/style.py +19 -0
- pythonnative/templates/android_template/app/build.gradle +4 -0
- pythonnative/templates/android_template/app/src/main/AndroidManifest.xml +4 -0
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +60 -0
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNFrameLayout.kt +50 -0
- pythonnative/templates/ios_template/ios_template/Info.plist +5 -0
- {pythonnative-0.26.0.dist-info → pythonnative-0.28.0.dist-info}/METADATA +3 -3
- {pythonnative-0.26.0.dist-info → pythonnative-0.28.0.dist-info}/RECORD +23 -22
- {pythonnative-0.26.0.dist-info → pythonnative-0.28.0.dist-info}/WHEEL +0 -0
- {pythonnative-0.26.0.dist-info → pythonnative-0.28.0.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.26.0.dist-info → pythonnative-0.28.0.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.26.0.dist-info → pythonnative-0.28.0.dist-info}/top_level.txt +0 -0
pythonnative/native_views/ios.py
CHANGED
|
@@ -14,20 +14,38 @@ is forwarded through
|
|
|
14
14
|
view's reconciler-assigned tag.
|
|
15
15
|
|
|
16
16
|
**Layout** is owned by the pure-Python flex engine in
|
|
17
|
-
`pythonnative.layout`: container handlers create
|
|
18
|
-
|
|
17
|
+
`pythonnative.layout`: container handlers create hit-test-aware
|
|
18
|
+
``UIView`` subclasses (see ``_PNInteractiveViewCTypes``), the engine
|
|
19
|
+
computes per-child frames in points, and
|
|
19
20
|
[`set_frame`][pythonnative.native_views.ios.IOSViewHandler.set_frame]
|
|
20
21
|
applies those frames via UIKit's classic ``frame`` property (with Auto
|
|
21
22
|
Layout disabled).
|
|
22
23
|
|
|
23
24
|
**Gestures** attach real ``UIGestureRecognizer`` instances (pan, pinch,
|
|
24
|
-
rotation, tap, long-press, swipe) configured from the
|
|
25
|
-
gesture specs, so recognition runs fully natively.
|
|
25
|
+
rotation, tap, long-press, swipe, fling) configured from the
|
|
26
|
+
serialized gesture specs, so recognition runs fully natively. The
|
|
27
|
+
composition metadata emitted by
|
|
28
|
+
[`serialize_gestures`][pythonnative.gestures.serialize_gestures] is
|
|
29
|
+
honored natively too: the shared delegate answers
|
|
30
|
+
``shouldRecognizeSimultaneously...`` from each spec's
|
|
31
|
+
``"simultaneous"`` set, and ``"wait_for"`` relationships map onto
|
|
32
|
+
``requireGestureRecognizerToFail:``.
|
|
33
|
+
|
|
34
|
+
**Interaction props**: ``pointer_events`` and ``hit_slop`` are
|
|
35
|
+
implemented by overriding ``pointInside:withEvent:`` and
|
|
36
|
+
``hitTest:withEvent:`` on the container subclass; ``z_index`` maps to
|
|
37
|
+
``layer.zPosition``. Per-corner radii (``border_top_left_radius`` and
|
|
38
|
+
friends) use ``layer.maskedCorners`` when one radius is shared and a
|
|
39
|
+
``CAShapeLayer`` mask when radii differ.
|
|
26
40
|
|
|
27
41
|
**Animations**: ``timing`` and ``spring`` specs are driven by UIKit
|
|
28
42
|
block animations with completion callbacks reported back through
|
|
29
43
|
``pythonnative.animated.native_animation_completed``; ``decay`` falls
|
|
30
|
-
back to the Python ticker.
|
|
44
|
+
back to the Python ticker. Python-driven frames arriving through
|
|
45
|
+
``set_animated_property`` accept floats for opacity and transform
|
|
46
|
+
channels, numeric degrees for ``rotate``, and ``"#RRGGBB"`` /
|
|
47
|
+
``"#AARRGGBB"`` strings for ``background_color`` and ``color``;
|
|
48
|
+
unknown properties are ignored.
|
|
31
49
|
|
|
32
50
|
This module is only imported on iOS at runtime. Desktop tests inject a
|
|
33
51
|
mock registry via
|
|
@@ -205,6 +223,10 @@ _add_method = _libobjc.class_addMethod
|
|
|
205
223
|
_add_method.restype = _ct.c_bool
|
|
206
224
|
_add_method.argtypes = [_ct.c_void_p, _ct.c_void_p, _ct.c_void_p, _ct.c_char_p]
|
|
207
225
|
|
|
226
|
+
_get_method_imp = _libobjc.class_getMethodImplementation
|
|
227
|
+
_get_method_imp.restype = _ct.c_void_p
|
|
228
|
+
_get_method_imp.argtypes = [_ct.c_void_p, _ct.c_void_p]
|
|
229
|
+
|
|
208
230
|
_objc_msgSend = _libobjc.objc_msgSend
|
|
209
231
|
|
|
210
232
|
_SEL_ALLOC = _sel_reg(b"alloc")
|
|
@@ -226,6 +248,11 @@ _SEL_TEXT_VIEW_DID_END = _sel_reg(b"textViewDidEndEditing:")
|
|
|
226
248
|
_SEL_TEXT_VIEW_DID_CHANGE = _sel_reg(b"textViewDidChange:")
|
|
227
249
|
|
|
228
250
|
_NS_OBJECT_CLS = _get_cls(b"NSObject")
|
|
251
|
+
_UIVIEW_CLS = _get_cls(b"UIView")
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class _PNCGPoint(_ct.Structure):
|
|
255
|
+
_fields_ = [("x", _ct.c_double), ("y", _ct.c_double)]
|
|
229
256
|
|
|
230
257
|
|
|
231
258
|
# ======================================================================
|
|
@@ -422,6 +449,139 @@ def _clamp_layer_corner_radius(layer: Any, width: float, height: float) -> None:
|
|
|
422
449
|
diagnostics.swallowed("ios._clamp_layer_corner_radius")
|
|
423
450
|
|
|
424
451
|
|
|
452
|
+
# ----------------------------------------------------------------------
|
|
453
|
+
# Per-corner radii
|
|
454
|
+
# ----------------------------------------------------------------------
|
|
455
|
+
#
|
|
456
|
+
# When every rounded corner shares one radius the cheap path applies:
|
|
457
|
+
# ``layer.cornerRadius`` restricted to the rounded corners through
|
|
458
|
+
# ``layer.maskedCorners``. When the radii differ, UIKit has no direct
|
|
459
|
+
# API, so the view's layer gets a ``CAShapeLayer`` mask built from a
|
|
460
|
+
# ``UIBezierPath`` with one arc per corner; the mask depends on the
|
|
461
|
+
# view's bounds, so ``set_frame`` rebuilds it on every frame change via
|
|
462
|
+
# ``_update_corner_mask``.
|
|
463
|
+
|
|
464
|
+
# Order matches ``_MASKED_CORNER_BITS``: top-left, top-right,
|
|
465
|
+
# bottom-left, bottom-right.
|
|
466
|
+
_CORNER_RADIUS_KEYS = (
|
|
467
|
+
"border_top_left_radius",
|
|
468
|
+
"border_top_right_radius",
|
|
469
|
+
"border_bottom_left_radius",
|
|
470
|
+
"border_bottom_right_radius",
|
|
471
|
+
)
|
|
472
|
+
# CACornerMask bits: kCALayerMinXMinYCorner (top-left) = 1,
|
|
473
|
+
# MaxXMinY (top-right) = 2, MinXMaxY (bottom-left) = 4,
|
|
474
|
+
# MaxXMaxY (bottom-right) = 8.
|
|
475
|
+
_MASKED_CORNER_BITS = (1, 2, 4, 8)
|
|
476
|
+
_ALL_MASKED_CORNERS = 15
|
|
477
|
+
|
|
478
|
+
# id(view) -> (tl, tr, bl, br) radii for views on the shape-mask path.
|
|
479
|
+
_pn_corner_radii_map: dict = {}
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def _apply_corner_radii(view: Any, merged: Dict[str, Any]) -> None:
|
|
483
|
+
"""Apply per-corner radii from the *merged* prop set.
|
|
484
|
+
|
|
485
|
+
Unspecified corners inherit the uniform ``border_radius`` (else 0),
|
|
486
|
+
matching the Android backend. With every per-corner key absent the
|
|
487
|
+
entry is dropped and the uniform path owns the corners again.
|
|
488
|
+
"""
|
|
489
|
+
if not any(merged.get(k) is not None for k in _CORNER_RADIUS_KEYS):
|
|
490
|
+
if _pn_corner_radii_map.pop(id(view), None) is not None:
|
|
491
|
+
try:
|
|
492
|
+
view.layer.setMask_(None)
|
|
493
|
+
view.layer.setMaskedCorners_(_ALL_MASKED_CORNERS)
|
|
494
|
+
except Exception:
|
|
495
|
+
diagnostics.swallowed("ios._apply_corner_radii")
|
|
496
|
+
_apply_view_border(view, merged)
|
|
497
|
+
return
|
|
498
|
+
base = float(merged.get("border_radius") or 0.0)
|
|
499
|
+
radii = tuple(
|
|
500
|
+
max(0.0, _safe_finite(merged[k], base)) if merged.get(k) is not None else max(0.0, base)
|
|
501
|
+
for k in _CORNER_RADIUS_KEYS
|
|
502
|
+
)
|
|
503
|
+
distinct = {r for r in radii if r > 0.0}
|
|
504
|
+
if len(distinct) <= 1:
|
|
505
|
+
# One shared radius (or all zero): plain cornerRadius limited
|
|
506
|
+
# to the rounded corners.
|
|
507
|
+
_pn_corner_radii_map.pop(id(view), None)
|
|
508
|
+
value = distinct.pop() if distinct else 0.0
|
|
509
|
+
mask = 0
|
|
510
|
+
for bit, r in zip(_MASKED_CORNER_BITS, radii):
|
|
511
|
+
if r > 0.0:
|
|
512
|
+
mask |= bit
|
|
513
|
+
try:
|
|
514
|
+
view.layer.setMask_(None)
|
|
515
|
+
view.layer.setCornerRadius_(value)
|
|
516
|
+
view.layer.setMaskedCorners_(mask if mask else _ALL_MASKED_CORNERS)
|
|
517
|
+
view.layer.setMasksToBounds_(True)
|
|
518
|
+
except Exception:
|
|
519
|
+
diagnostics.swallowed("ios._apply_corner_radii")
|
|
520
|
+
# Route the shared value through the frame-time pill clamp.
|
|
521
|
+
_pn_view_border_radius_map[id(view)] = value
|
|
522
|
+
return
|
|
523
|
+
# Differing radii: shape-layer mask, rebuilt at frame time.
|
|
524
|
+
_pn_view_border_radius_map.pop(id(view), None)
|
|
525
|
+
_pn_corner_radii_map[id(view)] = radii
|
|
526
|
+
try:
|
|
527
|
+
view.layer.setCornerRadius_(0.0)
|
|
528
|
+
view.layer.setMaskedCorners_(_ALL_MASKED_CORNERS)
|
|
529
|
+
except Exception:
|
|
530
|
+
diagnostics.swallowed("ios._apply_corner_radii")
|
|
531
|
+
try:
|
|
532
|
+
bounds = view.bounds
|
|
533
|
+
w = float(bounds.size.width)
|
|
534
|
+
h = float(bounds.size.height)
|
|
535
|
+
if w > 0.0 and h > 0.0:
|
|
536
|
+
_update_corner_mask(view, w, h)
|
|
537
|
+
except Exception:
|
|
538
|
+
diagnostics.swallowed("ios._apply_corner_radii")
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def _update_corner_mask(view: Any, width: float, height: float) -> None:
|
|
542
|
+
"""(Re)build the CAShapeLayer corner mask for the current bounds."""
|
|
543
|
+
radii = _pn_corner_radii_map.get(id(view))
|
|
544
|
+
if radii is None or width <= 0.0 or height <= 0.0:
|
|
545
|
+
return
|
|
546
|
+
tl, tr, bl, br = radii
|
|
547
|
+
# CSS-style scaling so adjacent radii never overlap along an edge.
|
|
548
|
+
scale = 1.0
|
|
549
|
+
for pair_sum, extent in ((tl + tr, width), (bl + br, width), (tl + bl, height), (tr + br, height)):
|
|
550
|
+
if pair_sum > extent > 0.0:
|
|
551
|
+
scale = min(scale, extent / pair_sum)
|
|
552
|
+
tl, tr, bl, br = (r * scale for r in radii)
|
|
553
|
+
try:
|
|
554
|
+
path = ObjCClass("UIBezierPath").bezierPath()
|
|
555
|
+
half_pi = math.pi / 2.0
|
|
556
|
+
path.moveToPoint_((tl, 0.0))
|
|
557
|
+
path.addLineToPoint_((width - tr, 0.0))
|
|
558
|
+
if tr > 0.0:
|
|
559
|
+
path.addArcWithCenter_radius_startAngle_endAngle_clockwise_((width - tr, tr), tr, -half_pi, 0.0, True)
|
|
560
|
+
path.addLineToPoint_((width, height - br))
|
|
561
|
+
if br > 0.0:
|
|
562
|
+
path.addArcWithCenter_radius_startAngle_endAngle_clockwise_(
|
|
563
|
+
(width - br, height - br), br, 0.0, half_pi, True
|
|
564
|
+
)
|
|
565
|
+
path.addLineToPoint_((bl, height))
|
|
566
|
+
if bl > 0.0:
|
|
567
|
+
path.addArcWithCenter_radius_startAngle_endAngle_clockwise_((bl, height - bl), bl, half_pi, math.pi, True)
|
|
568
|
+
path.addLineToPoint_((0.0, tl))
|
|
569
|
+
if tl > 0.0:
|
|
570
|
+
path.addArcWithCenter_radius_startAngle_endAngle_clockwise_((tl, tl), tl, math.pi, math.pi + half_pi, True)
|
|
571
|
+
path.closePath()
|
|
572
|
+
# On raw-registered outbound wrappers rubicon sometimes resolves
|
|
573
|
+
# ``CGPath`` as a bound method rather than a property.
|
|
574
|
+
cgpath = path.CGPath
|
|
575
|
+
if callable(cgpath):
|
|
576
|
+
cgpath = cgpath()
|
|
577
|
+
shape = ObjCClass("CAShapeLayer").layer()
|
|
578
|
+
shape.setFrame_(((0.0, 0.0), (width, height)))
|
|
579
|
+
shape.setPath_(cgpath)
|
|
580
|
+
view.layer.setMask_(shape)
|
|
581
|
+
except Exception:
|
|
582
|
+
diagnostics.swallowed("ios._update_corner_mask")
|
|
583
|
+
|
|
584
|
+
|
|
425
585
|
def _apply_shadow(view: Any, props: Dict[str, Any]) -> None:
|
|
426
586
|
"""Apply shadow_color/shadow_offset/shadow_opacity/shadow_radius via the view's layer.
|
|
427
587
|
|
|
@@ -666,13 +826,234 @@ def _apply_common_visual(view: Any, props: Dict[str, Any]) -> None:
|
|
|
666
826
|
view.setAlpha_(float(props["opacity"]))
|
|
667
827
|
except Exception:
|
|
668
828
|
diagnostics.swallowed("ios._apply_common_visual")
|
|
829
|
+
if "z_index" in props:
|
|
830
|
+
# A removed prop (None) restores the default plane.
|
|
831
|
+
try:
|
|
832
|
+
z = props["z_index"]
|
|
833
|
+
view.layer.setZPosition_(float(z) if z is not None else 0.0)
|
|
834
|
+
except Exception:
|
|
835
|
+
diagnostics.swallowed("ios._apply_common_visual")
|
|
836
|
+
if "pointer_events" in props or "hit_slop" in props:
|
|
837
|
+
_apply_interaction_props(view, props)
|
|
669
838
|
_apply_view_border(view, props)
|
|
670
839
|
_apply_side_borders(view, props)
|
|
840
|
+
# Per-corner radii read the merged prop set (an update may carry
|
|
841
|
+
# only one key); a lone border_radius change must also re-run the
|
|
842
|
+
# per-corner path when corner keys are active, or the uniform
|
|
843
|
+
# applier above would stomp the masked corners.
|
|
844
|
+
merged = _state_of(view).get("props") or props
|
|
845
|
+
if any(k in props for k in _CORNER_RADIUS_KEYS) or (
|
|
846
|
+
"border_radius" in props and any(merged.get(k) is not None for k in _CORNER_RADIUS_KEYS)
|
|
847
|
+
):
|
|
848
|
+
_apply_corner_radii(view, merged)
|
|
671
849
|
_apply_shadow(view, props)
|
|
672
850
|
_apply_transform(view, props)
|
|
673
851
|
_apply_accessibility(view, props)
|
|
674
852
|
|
|
675
853
|
|
|
854
|
+
# ======================================================================
|
|
855
|
+
# Interaction props: pointer_events / hit_slop (raw libobjc subclass)
|
|
856
|
+
# ======================================================================
|
|
857
|
+
#
|
|
858
|
+
# ``pointer_events`` and ``hit_slop`` need to change how UIKit
|
|
859
|
+
# hit-tests a view, which requires overriding ``pointInside:withEvent:``
|
|
860
|
+
# and ``hitTest:withEvent:``. Like ``_PNPortalViewCTypes``, the
|
|
861
|
+
# ``_PNInteractiveViewCTypes`` class is registered with raw libobjc
|
|
862
|
+
# (CFUNCTYPE IMPs) rather than rubicon's ``@objc_method``, for the same
|
|
863
|
+
# arm64 FFI reliability reasons documented in the module header.
|
|
864
|
+
#
|
|
865
|
+
# Container handlers (View / Row / Column, Pressable, SafeAreaView,
|
|
866
|
+
# KeyboardAvoidingView) *always* instantiate this subclass. That is the
|
|
867
|
+
# low-risk choice: with no entry in ``_pn_interaction_state`` both
|
|
868
|
+
# overrides reproduce stock UIView behavior exactly, and it means the
|
|
869
|
+
# props can appear, change, or disappear on any later render without
|
|
870
|
+
# recreating the view. Leaf handlers (labels, controls) keep their
|
|
871
|
+
# stock classes; if the props show up there, ``_apply_interaction_props``
|
|
872
|
+
# warns once and the props are ignored.
|
|
873
|
+
#
|
|
874
|
+
# The overrides read per-view state synchronously from
|
|
875
|
+
# ``_pn_interaction_state``, a Python dict keyed by the raw view
|
|
876
|
+
# pointer (the ``self`` of every IMP), following the same routing
|
|
877
|
+
# pattern as ``_pn_action_handlers``.
|
|
878
|
+
#
|
|
879
|
+
# Semantics (matching React Native and the Android backend):
|
|
880
|
+
#
|
|
881
|
+
# - ``"none"``: the view and its whole subtree are transparent to
|
|
882
|
+
# touches (``hitTest`` returns nil, ``pointInside`` returns NO).
|
|
883
|
+
# - ``"box_none"``: the view itself never receives touches, but
|
|
884
|
+
# subviews still do (``hitTest`` returns the subview result and
|
|
885
|
+
# converts a self result to nil).
|
|
886
|
+
# - ``"box_only"``: the view receives touches, subviews never do.
|
|
887
|
+
# - ``hit_slop`` expands ``pointInside`` by the slop on each edge.
|
|
888
|
+
# As on every platform, the expanded area only works where the
|
|
889
|
+
# parent's own bounds still contain the point.
|
|
890
|
+
|
|
891
|
+
# Maps view ptr -> {"pointer_events": mode or None,
|
|
892
|
+
# "hit_slop": (top, left, bottom, right) or None}.
|
|
893
|
+
_pn_interaction_state: Dict[int, Dict[str, Any]] = {}
|
|
894
|
+
|
|
895
|
+
_POINTER_EVENT_MODES = ("none", "box_none", "box_only")
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
def _hit_slop_insets(value: Any) -> Optional[Tuple[float, float, float, float]]:
|
|
899
|
+
"""Normalize a ``hit_slop`` prop into ``(top, left, bottom, right)`` points.
|
|
900
|
+
|
|
901
|
+
Accepts a uniform number or a dict with any of ``top`` / ``left`` /
|
|
902
|
+
``bottom`` / ``right``. Returns ``None`` for absent or unparseable
|
|
903
|
+
values.
|
|
904
|
+
"""
|
|
905
|
+
if value is None:
|
|
906
|
+
return None
|
|
907
|
+
if isinstance(value, dict):
|
|
908
|
+
try:
|
|
909
|
+
return (
|
|
910
|
+
float(value.get("top", 0.0) or 0.0),
|
|
911
|
+
float(value.get("left", 0.0) or 0.0),
|
|
912
|
+
float(value.get("bottom", 0.0) or 0.0),
|
|
913
|
+
float(value.get("right", 0.0) or 0.0),
|
|
914
|
+
)
|
|
915
|
+
except Exception:
|
|
916
|
+
return None
|
|
917
|
+
try:
|
|
918
|
+
uniform = float(value)
|
|
919
|
+
except Exception:
|
|
920
|
+
return None
|
|
921
|
+
return (uniform, uniform, uniform, uniform)
|
|
922
|
+
|
|
923
|
+
|
|
924
|
+
_INTERACTIVE_POINT_INSIDE_TYPE = _ct.CFUNCTYPE(_ct.c_bool, _ct.c_void_p, _ct.c_void_p, _PNCGPoint, _ct.c_void_p)
|
|
925
|
+
_INTERACTIVE_HITTEST_TYPE = _ct.CFUNCTYPE(_ct.c_void_p, _ct.c_void_p, _ct.c_void_p, _PNCGPoint, _ct.c_void_p)
|
|
926
|
+
|
|
927
|
+
_SEL_HITTEST_WITH_EVENT = _sel_reg(b"hitTest:withEvent:")
|
|
928
|
+
|
|
929
|
+
# UIView's own ``hitTest:withEvent:`` IMP, called directly (the
|
|
930
|
+
# superclass is known to be exactly UIView, so this is equivalent to a
|
|
931
|
+
# ``objc_msgSendSuper`` call without the extra struct plumbing). Bound
|
|
932
|
+
# after the class registration below.
|
|
933
|
+
_uiview_hittest_imp: Any = None
|
|
934
|
+
|
|
935
|
+
|
|
936
|
+
def _interactive_point_inside_imp(self_ptr: int, _cmd_ptr: int, point: Any, _event_ptr: int) -> bool:
|
|
937
|
+
"""``pointInside:withEvent:`` honoring pointer_events and hit_slop."""
|
|
938
|
+
try:
|
|
939
|
+
info = _pn_interaction_state.get(int(self_ptr or 0)) or {}
|
|
940
|
+
if info.get("pointer_events") == "none":
|
|
941
|
+
return False
|
|
942
|
+
top = left = bottom = right = 0.0
|
|
943
|
+
slop = info.get("hit_slop")
|
|
944
|
+
if slop is not None:
|
|
945
|
+
top, left, bottom, right = slop
|
|
946
|
+
view = ObjCInstance(_ct.c_void_p(self_ptr))
|
|
947
|
+
bounds = view.bounds
|
|
948
|
+
w = float(bounds.size.width)
|
|
949
|
+
h = float(bounds.size.height)
|
|
950
|
+
px = float(point.x)
|
|
951
|
+
py = float(point.y)
|
|
952
|
+
return (-left <= px <= w + right) and (-top <= py <= h + bottom)
|
|
953
|
+
except Exception:
|
|
954
|
+
return False
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
def _interactive_hittest_imp(self_ptr: int, _cmd_ptr: int, point: Any, event_ptr: int) -> int:
|
|
958
|
+
"""``hitTest:withEvent:`` honoring the pointer_events mode.
|
|
959
|
+
|
|
960
|
+
Delegates to UIView's stock implementation (which consults our
|
|
961
|
+
``pointInside:`` override, so hit_slop is already accounted for),
|
|
962
|
+
then filters the result per the mode.
|
|
963
|
+
"""
|
|
964
|
+
try:
|
|
965
|
+
info = _pn_interaction_state.get(int(self_ptr or 0)) or {}
|
|
966
|
+
mode = info.get("pointer_events")
|
|
967
|
+
if mode == "none":
|
|
968
|
+
return 0
|
|
969
|
+
if _uiview_hittest_imp is None:
|
|
970
|
+
return 0
|
|
971
|
+
result = int(_uiview_hittest_imp(self_ptr, _SEL_HITTEST_WITH_EVENT, point, event_ptr) or 0)
|
|
972
|
+
if mode == "box_none" and result == int(self_ptr or 0):
|
|
973
|
+
return 0
|
|
974
|
+
if mode == "box_only":
|
|
975
|
+
return int(self_ptr or 0) if result else 0
|
|
976
|
+
return result
|
|
977
|
+
except Exception:
|
|
978
|
+
return 0
|
|
979
|
+
|
|
980
|
+
|
|
981
|
+
_interactive_point_inside_imp_ref = _INTERACTIVE_POINT_INSIDE_TYPE(_interactive_point_inside_imp)
|
|
982
|
+
_interactive_hittest_imp_ref = _INTERACTIVE_HITTEST_TYPE(_interactive_hittest_imp)
|
|
983
|
+
|
|
984
|
+
_PN_INTERACTIVE_VIEW_CLS = _alloc_cls(_UIVIEW_CLS, b"_PNInteractiveViewCTypes", 0) if _UIVIEW_CLS else None
|
|
985
|
+
if _PN_INTERACTIVE_VIEW_CLS:
|
|
986
|
+
_add_method(
|
|
987
|
+
_PN_INTERACTIVE_VIEW_CLS,
|
|
988
|
+
_sel_reg(b"pointInside:withEvent:"),
|
|
989
|
+
_ct.cast(_interactive_point_inside_imp_ref, _ct.c_void_p),
|
|
990
|
+
b"c@:{CGPoint=dd}@",
|
|
991
|
+
)
|
|
992
|
+
_add_method(
|
|
993
|
+
_PN_INTERACTIVE_VIEW_CLS,
|
|
994
|
+
_SEL_HITTEST_WITH_EVENT,
|
|
995
|
+
_ct.cast(_interactive_hittest_imp_ref, _ct.c_void_p),
|
|
996
|
+
b"@@:{CGPoint=dd}@",
|
|
997
|
+
)
|
|
998
|
+
_reg_cls(_PN_INTERACTIVE_VIEW_CLS)
|
|
999
|
+
try:
|
|
1000
|
+
_raw_hittest = _get_method_imp(_UIVIEW_CLS, _SEL_HITTEST_WITH_EVENT)
|
|
1001
|
+
if _raw_hittest:
|
|
1002
|
+
_uiview_hittest_imp = _ct.cast(_ct.c_void_p(_raw_hittest), _INTERACTIVE_HITTEST_TYPE)
|
|
1003
|
+
except Exception:
|
|
1004
|
+
_uiview_hittest_imp = None
|
|
1005
|
+
|
|
1006
|
+
|
|
1007
|
+
def _new_container_view() -> Any:
|
|
1008
|
+
"""Instantiate the hit-test-aware container class (UIView fallback).
|
|
1009
|
+
|
|
1010
|
+
When the ``_PNInteractiveViewCTypes`` registration is unavailable
|
|
1011
|
+
the container degrades to a plain ``UIView``: everything works
|
|
1012
|
+
except ``pointer_events`` / ``hit_slop``, which warn when applied.
|
|
1013
|
+
"""
|
|
1014
|
+
view = None
|
|
1015
|
+
if _PN_INTERACTIVE_VIEW_CLS:
|
|
1016
|
+
try:
|
|
1017
|
+
view = ObjCClass("_PNInteractiveViewCTypes").alloc().init()
|
|
1018
|
+
except Exception:
|
|
1019
|
+
view = None
|
|
1020
|
+
if view is None:
|
|
1021
|
+
view = ObjCClass("UIView").alloc().init()
|
|
1022
|
+
view.setTranslatesAutoresizingMaskIntoConstraints_(True)
|
|
1023
|
+
return view
|
|
1024
|
+
|
|
1025
|
+
|
|
1026
|
+
def _apply_interaction_props(view: Any, props: Dict[str, Any]) -> None:
|
|
1027
|
+
"""Record pointer_events / hit_slop where the ObjC overrides read them."""
|
|
1028
|
+
ptr = _objc_ptr(view)
|
|
1029
|
+
if ptr is None:
|
|
1030
|
+
return
|
|
1031
|
+
entry = _pn_interaction_state.setdefault(ptr, {})
|
|
1032
|
+
if "pointer_events" in props:
|
|
1033
|
+
mode = props["pointer_events"]
|
|
1034
|
+
entry["pointer_events"] = mode if mode in _POINTER_EVENT_MODES else None
|
|
1035
|
+
if "hit_slop" in props:
|
|
1036
|
+
entry["hit_slop"] = _hit_slop_insets(props["hit_slop"])
|
|
1037
|
+
if entry.get("pointer_events") is None and entry.get("hit_slop") is None:
|
|
1038
|
+
# Both props removed (or "auto"): drop the entry so the
|
|
1039
|
+
# overrides take their stock-behavior fast path.
|
|
1040
|
+
_pn_interaction_state.pop(ptr, None)
|
|
1041
|
+
return
|
|
1042
|
+
is_custom = False
|
|
1043
|
+
try:
|
|
1044
|
+
if _PN_INTERACTIVE_VIEW_CLS:
|
|
1045
|
+
is_custom = bool(view.isKindOfClass_(ObjCClass("_PNInteractiveViewCTypes")))
|
|
1046
|
+
except Exception:
|
|
1047
|
+
is_custom = False
|
|
1048
|
+
if not is_custom:
|
|
1049
|
+
diagnostics.warn_once(
|
|
1050
|
+
"pointer_events / hit_slop only work on container elements "
|
|
1051
|
+
"(View, Row, Column, Pressable) on iOS; this element's native "
|
|
1052
|
+
"class doesn't override hit-testing, so the props are ignored",
|
|
1053
|
+
key=f"interaction_props:{_tag_of(view)}",
|
|
1054
|
+
)
|
|
1055
|
+
|
|
1056
|
+
|
|
676
1057
|
# ======================================================================
|
|
677
1058
|
# Retention + shared targets
|
|
678
1059
|
# ======================================================================
|
|
@@ -684,17 +1065,42 @@ _pn_retained_views: list = []
|
|
|
684
1065
|
# Simultaneous-recognition gesture delegate (raw libobjc)
|
|
685
1066
|
# ======================================================================
|
|
686
1067
|
#
|
|
687
|
-
#
|
|
688
|
-
#
|
|
689
|
-
#
|
|
690
|
-
#
|
|
691
|
-
#
|
|
1068
|
+
# UIKit defaults to recognizer exclusivity, so every recognizer we
|
|
1069
|
+
# create gets this delegate. The answer to
|
|
1070
|
+
# ``gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:``
|
|
1071
|
+
# mirrors the GestureArbiter's semantics on Android/desktop:
|
|
1072
|
+
#
|
|
1073
|
+
# - Recognizers wired by ``_wire_gestures`` carry an entry in
|
|
1074
|
+
# ``_pn_gesture_relations`` (view pointer, spec index, and the spec's
|
|
1075
|
+
# ``"simultaneous"`` index set). Two recognizers on the *same* view
|
|
1076
|
+
# recognize together only when each is in the other's set; a spec
|
|
1077
|
+
# without the key means "simultaneous with everything" (back-compat
|
|
1078
|
+
# for hand-built spec dicts).
|
|
1079
|
+
# - Any pairing involving an unknown recognizer (Pressable's built-in
|
|
1080
|
+
# press trio, a scroll view's pan, recognizers from another view)
|
|
1081
|
+
# keeps the historical always-YES behavior.
|
|
692
1082
|
|
|
693
1083
|
_GESTURE_SIMUL_TYPE = _ct.CFUNCTYPE(_ct.c_bool, _ct.c_void_p, _ct.c_void_p, _ct.c_void_p, _ct.c_void_p)
|
|
694
1084
|
|
|
1085
|
+
# Maps recognizer ptr -> (view ptr, spec index, simultaneous set or None).
|
|
1086
|
+
_pn_gesture_relations: Dict[int, Tuple[int, int, Optional[frozenset]]] = {}
|
|
1087
|
+
|
|
695
1088
|
|
|
696
1089
|
def _gesture_simul_imp(self_ptr: int, cmd_ptr: int, g1_ptr: int, g2_ptr: int) -> bool:
|
|
697
|
-
|
|
1090
|
+
try:
|
|
1091
|
+
a = _pn_gesture_relations.get(int(g1_ptr or 0))
|
|
1092
|
+
b = _pn_gesture_relations.get(int(g2_ptr or 0))
|
|
1093
|
+
if a is None or b is None:
|
|
1094
|
+
return True
|
|
1095
|
+
if a[0] != b[0]:
|
|
1096
|
+
return True
|
|
1097
|
+
sim_a = a[2]
|
|
1098
|
+
sim_b = b[2]
|
|
1099
|
+
if sim_a is None or sim_b is None:
|
|
1100
|
+
return True
|
|
1101
|
+
return b[1] in sim_a and a[1] in sim_b
|
|
1102
|
+
except Exception:
|
|
1103
|
+
return True
|
|
698
1104
|
|
|
699
1105
|
|
|
700
1106
|
_gesture_simul_imp_ref = _GESTURE_SIMUL_TYPE(_gesture_simul_imp)
|
|
@@ -912,6 +1318,7 @@ def _make_gesture_handler(
|
|
|
912
1318
|
kind: str,
|
|
913
1319
|
index: int,
|
|
914
1320
|
direction: Optional[str] = None,
|
|
1321
|
+
n_pointers: Optional[int] = None,
|
|
915
1322
|
) -> Any:
|
|
916
1323
|
"""Build the action closure emitting one ``gesture:<i>`` payload."""
|
|
917
1324
|
|
|
@@ -962,6 +1369,13 @@ def _make_gesture_handler(
|
|
|
962
1369
|
# per-recognizer direction is the actual swipe direction.
|
|
963
1370
|
payload["state"] = "ended"
|
|
964
1371
|
payload["direction"] = direction
|
|
1372
|
+
elif kind == "fling":
|
|
1373
|
+
# Same discrete shape as swipe. ``numberOfTouches`` is often
|
|
1374
|
+
# 0 by recognition time, so report the configured pointer
|
|
1375
|
+
# requirement (which UIKit enforced) instead.
|
|
1376
|
+
payload["state"] = "ended"
|
|
1377
|
+
payload["direction"] = direction
|
|
1378
|
+
payload["pointer_count"] = int(n_pointers or 1)
|
|
965
1379
|
elif kind == "tap":
|
|
966
1380
|
payload["state"] = "ended"
|
|
967
1381
|
|
|
@@ -973,9 +1387,17 @@ def _make_gesture_handler(
|
|
|
973
1387
|
def _make_recognizer(kind: str, spec: Dict[str, Any]) -> List[Tuple[Any, Optional[str]]]:
|
|
974
1388
|
"""Build the UIGestureRecognizer(s) for one serialized gesture spec.
|
|
975
1389
|
|
|
976
|
-
Swipe with ``direction="any"``
|
|
977
|
-
(
|
|
978
|
-
|
|
1390
|
+
Swipe and fling with ``direction="any"`` need one recognizer per
|
|
1391
|
+
direction (a ``UISwipeGestureRecognizer`` reports no resolved
|
|
1392
|
+
direction, only its configured mask), so this returns
|
|
1393
|
+
``(recognizer, direction)`` pairs; ``direction`` is ``None`` for
|
|
1394
|
+
the other kinds.
|
|
1395
|
+
|
|
1396
|
+
Fling maps to ``UISwipeGestureRecognizer`` with
|
|
1397
|
+
``numberOfTouchesRequired``. The spec's ``min_velocity`` can't be
|
|
1398
|
+
enforced: UIKit exposes no velocity knob on swipe recognizers, so
|
|
1399
|
+
iOS uses the system's own swipe velocity threshold (a documented
|
|
1400
|
+
approximation; Android and desktop honor the exact value).
|
|
979
1401
|
"""
|
|
980
1402
|
out: List[Tuple[Any, Optional[str]]] = []
|
|
981
1403
|
if kind == "tap":
|
|
@@ -1010,6 +1432,18 @@ def _make_recognizer(kind: str, spec: Dict[str, Any]) -> List[Tuple[Any, Optiona
|
|
|
1010
1432
|
except Exception:
|
|
1011
1433
|
diagnostics.swallowed("ios._make_recognizer")
|
|
1012
1434
|
out.append((rec, d))
|
|
1435
|
+
elif kind == "fling":
|
|
1436
|
+
direction = str(spec.get("direction", "any"))
|
|
1437
|
+
directions = [direction] if direction in _SWIPE_DIRECTIONS else list(_SWIPE_DIRECTIONS)
|
|
1438
|
+
n_pointers = max(1, int(spec.get("n_pointers", 1)))
|
|
1439
|
+
for d in directions:
|
|
1440
|
+
rec = ObjCClass("UISwipeGestureRecognizer").alloc().init()
|
|
1441
|
+
try:
|
|
1442
|
+
rec.setDirection_(_SWIPE_DIRECTIONS[d])
|
|
1443
|
+
rec.setNumberOfTouchesRequired_(n_pointers)
|
|
1444
|
+
except Exception:
|
|
1445
|
+
diagnostics.swallowed("ios._make_recognizer")
|
|
1446
|
+
out.append((rec, d))
|
|
1013
1447
|
elif kind == "pinch":
|
|
1014
1448
|
out.append((ObjCClass("UIPinchGestureRecognizer").alloc().init(), None))
|
|
1015
1449
|
elif kind == "rotation":
|
|
@@ -1023,6 +1457,15 @@ def _wire_gestures(view: Any, specs: Any) -> None:
|
|
|
1023
1457
|
Re-wiring on update removes previously attached PythonNative
|
|
1024
1458
|
recognizers first (configuration changes are rare; correctness
|
|
1025
1459
|
beats incremental patching here).
|
|
1460
|
+
|
|
1461
|
+
Composition metadata is applied in two passes: while creating each
|
|
1462
|
+
recognizer its ``(view, index, simultaneous-set)`` triple is
|
|
1463
|
+
recorded in ``_pn_gesture_relations`` for the shared delegate, and
|
|
1464
|
+
once every recognizer exists, each spec's ``"wait_for"`` indices
|
|
1465
|
+
are wired with ``requireGestureRecognizerToFail:`` (every
|
|
1466
|
+
recognizer of the waiting spec requires every recognizer of each
|
|
1467
|
+
target spec to fail; a spec can own several recognizers, e.g. an
|
|
1468
|
+
any-direction swipe).
|
|
1026
1469
|
"""
|
|
1027
1470
|
state = _state_of(view)
|
|
1028
1471
|
for rec in state.get("gesture_recognizers") or []:
|
|
@@ -1031,6 +1474,7 @@ def _wire_gestures(view: Any, specs: Any) -> None:
|
|
|
1031
1474
|
except Exception:
|
|
1032
1475
|
diagnostics.swallowed("ios._wire_gestures")
|
|
1033
1476
|
_pn_action_handlers.pop(_recognizer_ptr(rec), None)
|
|
1477
|
+
_pn_gesture_relations.pop(_recognizer_ptr(rec), None)
|
|
1034
1478
|
state["gesture_recognizers"] = []
|
|
1035
1479
|
if not isinstance(specs, (list, tuple)) or not specs:
|
|
1036
1480
|
return
|
|
@@ -1040,13 +1484,18 @@ def _wire_gestures(view: Any, specs: Any) -> None:
|
|
|
1040
1484
|
except Exception:
|
|
1041
1485
|
diagnostics.swallowed("ios._wire_gestures")
|
|
1042
1486
|
|
|
1487
|
+
view_ptr = _objc_ptr(view) or 0
|
|
1043
1488
|
recognizers: List[Any] = []
|
|
1489
|
+
recs_by_index: Dict[int, List[Any]] = {}
|
|
1044
1490
|
for i, spec in enumerate(specs):
|
|
1045
1491
|
if not isinstance(spec, dict):
|
|
1046
1492
|
continue
|
|
1047
1493
|
kind = str(spec.get("kind", ""))
|
|
1048
1494
|
if not kind:
|
|
1049
1495
|
continue
|
|
1496
|
+
sim = spec.get("simultaneous")
|
|
1497
|
+
sim_set = None if sim is None else frozenset(int(s) for s in sim)
|
|
1498
|
+
n_pointers = max(1, int(spec.get("n_pointers", 1) or 1)) if kind == "fling" else None
|
|
1050
1499
|
for rec, direction in _make_recognizer(kind, spec):
|
|
1051
1500
|
try:
|
|
1052
1501
|
rec.setCancelsTouchesInView_(False)
|
|
@@ -1059,7 +1508,21 @@ def _wire_gestures(view: Any, specs: Any) -> None:
|
|
|
1059
1508
|
recognizers.append(rec)
|
|
1060
1509
|
except Exception:
|
|
1061
1510
|
continue
|
|
1062
|
-
|
|
1511
|
+
_pn_gesture_relations[_recognizer_ptr(rec)] = (view_ptr, i, sim_set)
|
|
1512
|
+
recs_by_index.setdefault(i, []).append(rec)
|
|
1513
|
+
_register_action(rec, _make_gesture_handler(rec, view, kind, i, direction, n_pointers))
|
|
1514
|
+
|
|
1515
|
+
for i, spec in enumerate(specs):
|
|
1516
|
+
if not isinstance(spec, dict):
|
|
1517
|
+
continue
|
|
1518
|
+
for target_index in spec.get("wait_for") or ():
|
|
1519
|
+
targets = recs_by_index.get(int(target_index)) or []
|
|
1520
|
+
for waiter in recs_by_index.get(i) or []:
|
|
1521
|
+
for target in targets:
|
|
1522
|
+
try:
|
|
1523
|
+
waiter.requireGestureRecognizerToFail_(target)
|
|
1524
|
+
except Exception:
|
|
1525
|
+
diagnostics.swallowed("ios._wire_gestures")
|
|
1063
1526
|
|
|
1064
1527
|
state["gesture_recognizers"] = recognizers
|
|
1065
1528
|
|
|
@@ -1076,6 +1539,16 @@ def _is_main_thread() -> bool:
|
|
|
1076
1539
|
|
|
1077
1540
|
|
|
1078
1541
|
def _animated_applier_for(prop: str, value: Any) -> Optional[Callable[[Any], None]]:
|
|
1542
|
+
"""Build the per-frame applier for one animated property.
|
|
1543
|
+
|
|
1544
|
+
Color-valued frames (``background_color`` and ``color``) arrive as
|
|
1545
|
+
``"#RRGGBB"`` / ``"#AARRGGBB"`` strings from ``AnimatedInterpolation``
|
|
1546
|
+
color outputs and go through the same ``_uicolor`` parsing the
|
|
1547
|
+
ordinary update path uses. ``rotate`` receives numeric degrees,
|
|
1548
|
+
which is what ``_apply_transform`` expects for bare numbers. An
|
|
1549
|
+
unknown property returns ``None`` and the frame is silently
|
|
1550
|
+
ignored.
|
|
1551
|
+
"""
|
|
1079
1552
|
if prop == "opacity":
|
|
1080
1553
|
v = float(value)
|
|
1081
1554
|
|
|
@@ -1088,6 +1561,18 @@ def _animated_applier_for(prop: str, value: Any) -> Optional[Callable[[Any], Non
|
|
|
1088
1561
|
def _apply(view: Any) -> None:
|
|
1089
1562
|
view.setBackgroundColor_(_uicolor(value))
|
|
1090
1563
|
|
|
1564
|
+
return _apply
|
|
1565
|
+
if prop == "color":
|
|
1566
|
+
|
|
1567
|
+
def _apply(view: Any) -> None:
|
|
1568
|
+
color = _uicolor(value)
|
|
1569
|
+
try:
|
|
1570
|
+
view.setTextColor_(color)
|
|
1571
|
+
except Exception:
|
|
1572
|
+
# UIButton has no setTextColor:; views with neither
|
|
1573
|
+
# selector raise into the caller's swallow.
|
|
1574
|
+
view.setTitleColor_forState_(color, 0)
|
|
1575
|
+
|
|
1091
1576
|
return _apply
|
|
1092
1577
|
if prop in ("translate_x", "translate_y", "scale", "scale_x", "scale_y", "rotate"):
|
|
1093
1578
|
spec = {prop: value}
|
|
@@ -1229,11 +1714,14 @@ class IOSViewHandler(ViewHandler):
|
|
|
1229
1714
|
state = _state_of(native_view)
|
|
1230
1715
|
for rec in state.get("gesture_recognizers") or []:
|
|
1231
1716
|
_pn_action_handlers.pop(_recognizer_ptr(rec), None)
|
|
1717
|
+
_pn_gesture_relations.pop(_recognizer_ptr(rec), None)
|
|
1232
1718
|
# Controls register their own pointer as the action-handler key
|
|
1233
1719
|
# (see _register_control_action); drop it with the view.
|
|
1234
1720
|
_pn_action_handlers.pop(_recognizer_ptr(native_view), None)
|
|
1235
1721
|
_pn_last_control_event_ts.pop(_recognizer_ptr(native_view), None)
|
|
1722
|
+
_pn_interaction_state.pop(_objc_ptr(native_view) or 0, None)
|
|
1236
1723
|
_pn_view_border_radius_map.pop(id(native_view), None)
|
|
1724
|
+
_pn_corner_radii_map.pop(id(native_view), None)
|
|
1237
1725
|
_pn_side_border_map.pop(id(native_view), None)
|
|
1238
1726
|
try:
|
|
1239
1727
|
native_view.removeFromSuperview()
|
|
@@ -1284,6 +1772,9 @@ class IOSViewHandler(ViewHandler):
|
|
|
1284
1772
|
native_view.setTranslatesAutoresizingMaskIntoConstraints_(True)
|
|
1285
1773
|
native_view.setFrame_(((frame_x, frame_y), (frame_w, frame_h)))
|
|
1286
1774
|
_clamp_view_corner_radius(native_view, frame_w, frame_h)
|
|
1775
|
+
# The per-corner shape mask is bounds-dependent; rebuild it
|
|
1776
|
+
# whenever the frame changes.
|
|
1777
|
+
_update_corner_mask(native_view, frame_w, frame_h)
|
|
1287
1778
|
_update_side_border_layers(native_view, frame_w, frame_h)
|
|
1288
1779
|
try:
|
|
1289
1780
|
_clamp_layer_corner_radius(native_view.layer, frame_w, frame_h)
|
|
@@ -1328,7 +1819,14 @@ class IOSViewHandler(ViewHandler):
|
|
|
1328
1819
|
return (0.0, 0.0)
|
|
1329
1820
|
|
|
1330
1821
|
def set_animated_property(self, native_view: Any, prop_name: str, value: Any) -> None:
|
|
1331
|
-
"""Apply one Python-driven animation frame immediately.
|
|
1822
|
+
"""Apply one Python-driven animation frame immediately.
|
|
1823
|
+
|
|
1824
|
+
Accepts floats for ``opacity`` and the transform channels,
|
|
1825
|
+
numeric degrees for ``rotate``, and ``"#RRGGBB"`` /
|
|
1826
|
+
``"#AARRGGBB"`` strings for ``background_color`` and ``color``
|
|
1827
|
+
(see ``_animated_applier_for``). Unknown properties are
|
|
1828
|
+
silently ignored.
|
|
1829
|
+
"""
|
|
1332
1830
|
if native_view is None:
|
|
1333
1831
|
return
|
|
1334
1832
|
try:
|
|
@@ -1389,17 +1887,18 @@ class IOSViewHandler(ViewHandler):
|
|
|
1389
1887
|
|
|
1390
1888
|
|
|
1391
1889
|
class FlexContainerHandler(IOSViewHandler):
|
|
1392
|
-
"""Container for flex layout, a
|
|
1890
|
+
"""Container for flex layout, a hit-test-aware ``UIView`` subclass.
|
|
1393
1891
|
|
|
1394
1892
|
All flex semantics (direction, alignment, distribution, padding)
|
|
1395
1893
|
are computed by the layout engine and applied via
|
|
1396
1894
|
[`set_frame`][pythonnative.native_views.ios.IOSViewHandler.set_frame].
|
|
1895
|
+
The view is a ``_PNInteractiveViewCTypes`` instance so
|
|
1896
|
+
``pointer_events`` and ``hit_slop`` can be applied on any render
|
|
1897
|
+
without recreating the view (see the interaction-props section).
|
|
1397
1898
|
"""
|
|
1398
1899
|
|
|
1399
1900
|
def _build(self, props: Dict[str, Any]) -> Any:
|
|
1400
|
-
|
|
1401
|
-
v.setTranslatesAutoresizingMaskIntoConstraints_(True)
|
|
1402
|
-
return v
|
|
1901
|
+
return _new_container_view()
|
|
1403
1902
|
|
|
1404
1903
|
|
|
1405
1904
|
# ======================================================================
|
|
@@ -2586,8 +3085,7 @@ class PressableHandler(IOSViewHandler):
|
|
|
2586
3085
|
"""
|
|
2587
3086
|
|
|
2588
3087
|
def _build(self, props: Dict[str, Any]) -> Any:
|
|
2589
|
-
v =
|
|
2590
|
-
v.setTranslatesAutoresizingMaskIntoConstraints_(True)
|
|
3088
|
+
v = _new_container_view()
|
|
2591
3089
|
v.setUserInteractionEnabled_(True)
|
|
2592
3090
|
self._wire_press(v)
|
|
2593
3091
|
return v
|
|
@@ -3044,9 +3542,7 @@ class SafeAreaViewHandler(IOSViewHandler):
|
|
|
3044
3542
|
"""Plain container; safe-area insets are applied by the layout engine."""
|
|
3045
3543
|
|
|
3046
3544
|
def _build(self, props: Dict[str, Any]) -> Any:
|
|
3047
|
-
|
|
3048
|
-
v.setTranslatesAutoresizingMaskIntoConstraints_(True)
|
|
3049
|
-
return v
|
|
3545
|
+
return _new_container_view()
|
|
3050
3546
|
|
|
3051
3547
|
|
|
3052
3548
|
# ======================================================================
|
|
@@ -3220,10 +3716,6 @@ class ModalHandler(IOSViewHandler):
|
|
|
3220
3716
|
# other delegate classes in this module.
|
|
3221
3717
|
|
|
3222
3718
|
|
|
3223
|
-
class _PNCGPoint(_ct.Structure):
|
|
3224
|
-
_fields_ = [("x", _ct.c_double), ("y", _ct.c_double)]
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
3719
|
_PORTAL_POINT_INSIDE_TYPE = _ct.CFUNCTYPE(_ct.c_bool, _ct.c_void_p, _ct.c_void_p, _PNCGPoint, _ct.c_void_p)
|
|
3228
3720
|
|
|
3229
3721
|
|
|
@@ -3261,7 +3753,6 @@ def _portal_point_inside_imp(self_ptr: int, _cmd_ptr: int, point: Any, _event_pt
|
|
|
3261
3753
|
|
|
3262
3754
|
_portal_point_inside_imp_ref = _PORTAL_POINT_INSIDE_TYPE(_portal_point_inside_imp)
|
|
3263
3755
|
|
|
3264
|
-
_UIVIEW_CLS = _get_cls(b"UIView")
|
|
3265
3756
|
_PN_PORTAL_VIEW_CLS = _alloc_cls(_UIVIEW_CLS, b"_PNPortalViewCTypes", 0) if _UIVIEW_CLS else None
|
|
3266
3757
|
if _PN_PORTAL_VIEW_CLS:
|
|
3267
3758
|
_add_method(
|
|
@@ -3497,9 +3988,7 @@ class KeyboardAvoidingViewHandler(IOSViewHandler):
|
|
|
3497
3988
|
|
|
3498
3989
|
def _build(self, props: Dict[str, Any]) -> Any:
|
|
3499
3990
|
_ensure_keyboard_observer()
|
|
3500
|
-
|
|
3501
|
-
v.setTranslatesAutoresizingMaskIntoConstraints_(True)
|
|
3502
|
-
return v
|
|
3991
|
+
return _new_container_view()
|
|
3503
3992
|
|
|
3504
3993
|
|
|
3505
3994
|
# ======================================================================
|