| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent ec8aa23 commit 7ef22ae
1 file changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -754,6 +754,7 @@ def _set_recognizer_delegate(recognizer: Any) -> None: | |||
| 754 | 754 | _pn_action_handlers: Dict[int, Any] = {} | |
| 755 | 755 | ||
| 756 | 756 | _ACTION_IMP_TYPE = _ct.CFUNCTYPE(None, _ct.c_void_p, _ct.c_void_p, _ct.c_void_p) | |
| 757 | + _CONTROL_ACTION_IMP_TYPE = _ct.CFUNCTYPE(None, _ct.c_void_p, _ct.c_void_p, _ct.c_void_p, _ct.c_void_p) | ||
| 757 | 758 | ||
| 758 | 759 | ||
| 759 | 760 | def _action_imp(_self_ptr: int, _cmd_ptr: int, sender_ptr: int) -> None: | |
@@ -769,7 +770,65 @@ def _action_imp(_self_ptr: int, _cmd_ptr: int, sender_ptr: int) -> None: | |||
| 769 | 770 | ||
| 770 | 771 | _action_imp_ref = _ACTION_IMP_TYPE(_action_imp) | |
| 771 | 772 | ||
| 773 | + # ---------------------------------------------------------------------- | ||
| 774 | + # UIControl action dedupe | ||
| 775 | + # ---------------------------------------------------------------------- | ||
| 776 | + # | ||
| 777 | + # The simulator's XCTest event-injection stack sometimes delivers a | ||
| 778 | + # single synthesized tap as *two* back-to-back "send control actions" | ||
| 779 | + # bursts for the same UIEvent (~1ms apart; the unified log shows one | ||
| 780 | + # UIEvent delivered to one window, then the doubled sends). Every | ||
| 781 | + # non-idempotent ``on_press`` then fires twice: reducer counters jump | ||
| 782 | + # by two, alerts present twice, pickers reopen after selecting. The | ||
| 783 | + # control action selector therefore takes the ``forEvent:`` argument | ||
| 784 | + # and drops a second send whose UIEvent timestamp matches the last one | ||
| 785 | + # delivered to the same control. Distinct taps (even rapid ones) carry | ||
| 786 | + # distinct event timestamps, and continuous ValueChanged streams | ||
| 787 | + # (slider drags) tick the timestamp per touch move, so only the | ||
| 788 | + # duplicated sends are filtered. Programmatic ``sendActions...`` calls | ||
| 789 | + # pass a nil event and are never deduped. | ||
| 790 | + | ||
| 791 | + # Maps control ptr -> UIEvent.timestamp of the last delivered action. | ||
| 792 | + _pn_last_control_event_ts: Dict[int, float] = {} | ||
| 793 | + | ||
| 794 | + _objc_msgSend_double = _ct.CFUNCTYPE(_ct.c_double, _ct.c_void_p, _ct.c_void_p)(("objc_msgSend", _libobjc)) | ||
| 795 | + | ||
| 796 | + _SEL_TIMESTAMP = _sel_reg(b"timestamp") | ||
| 797 | + | ||
| 798 | + | ||
| 799 | + def _control_action_imp(_self_ptr: int, _cmd_ptr: int, sender_ptr: int, event_ptr: int) -> None: | ||
| 800 | + """Raw C callback for every PythonNative UIControl event action.""" | ||
| 801 | + key = int(sender_ptr or 0) | ||
| 802 | + handler = _pn_action_handlers.get(key) | ||
| 803 | + if handler is None: | ||
| 804 | + return | ||
| 805 | + if event_ptr: | ||
| 806 | + try: | ||
| 807 | + timestamp = float(_objc_msgSend_double(event_ptr, _SEL_TIMESTAMP)) | ||
| 808 | + except Exception: | ||
| 809 | + timestamp = None | ||
| 810 | + if timestamp is not None: | ||
| 811 | + if _pn_last_control_event_ts.get(key) == timestamp: | ||
| 812 | + diagnostics.warn_once( | ||
| 813 | + "ios._control_action_imp dropped a duplicated control action " | ||
| 814 | + "(one UIEvent, two sends; a simulator event-injection quirk)", | ||
| 815 | + key=f"control_action_dedupe:{key}", | ||
| 816 | + ) | ||
| 817 | + return | ||
| 818 | + _pn_last_control_event_ts[key] = timestamp | ||
| 819 | + try: | ||
| 820 | + handler() | ||
| 821 | + except Exception: | ||
| 822 | + diagnostics.swallowed("ios._control_action_imp") | ||
| 823 | + | ||
| 824 | + | ||
| 825 | + _control_action_imp_ref = _CONTROL_ACTION_IMP_TYPE(_control_action_imp) | ||
| 826 | + | ||
| 827 | + # Gesture recognizers only support zero- or one-argument action | ||
| 828 | + # selectors, so recognizers keep the plain variant while controls use | ||
| 829 | + # the two-argument ``forEvent:`` variant for the dedupe above. | ||
| 772 | 830 | _SEL_ON_ACTION = _sel_reg(b"onPNAction:") | |
| 831 | + _SEL_ON_CONTROL_ACTION = _sel_reg(b"onPNControlAction:forEvent:") | ||
| 773 | 832 | ||
| 774 | 833 | _PN_ACTION_TARGET_CLS = _alloc_cls(_NS_OBJECT_CLS, b"_PNActionTargetCTypes", 0) | |
| 775 | 834 | if _PN_ACTION_TARGET_CLS: | |
@@ -779,6 +838,12 @@ def _action_imp(_self_ptr: int, _cmd_ptr: int, sender_ptr: int) -> None: | |||
| 779 | 838 | _ct.cast(_action_imp_ref, _ct.c_void_p), | |
| 780 | 839 | b"v@:@", | |
| 781 | 840 | ) | |
| 841 | + _add_method( | ||
| 842 | + _PN_ACTION_TARGET_CLS, | ||
| 843 | + _SEL_ON_CONTROL_ACTION, | ||
| 844 | + _ct.cast(_control_action_imp_ref, _ct.c_void_p), | ||
| 845 | + b"v@:@@", | ||
| 846 | + ) | ||
| 782 | 847 | _reg_cls(_PN_ACTION_TARGET_CLS) | |
| 783 | 848 | ||
| 784 | 849 | _pn_action_target_ptr: Any = None | |
@@ -831,7 +896,7 @@ def _register_control_action(control: Any, events_mask: int, handler: Any) -> No | |||
| 831 | 896 | _objc_msgSend.restype = None | |
| 832 | 897 | _objc_msgSend.argtypes = [_ct.c_void_p, _ct.c_void_p, _ct.c_void_p, _ct.c_ulong] | |
| 833 | 898 | ctl_ptr = control.ptr if hasattr(control, "ptr") else control | |
| 834 | - _objc_msgSend(ctl_ptr, _SEL_ADD_TARGET_ACTION_EVENTS, target_ptr, _SEL_ON_ACTION, events_mask) | ||
| 899 | + _objc_msgSend(ctl_ptr, _SEL_ADD_TARGET_ACTION_EVENTS, target_ptr, _SEL_ON_CONTROL_ACTION, events_mask) | ||
| 835 | 900 | _pn_action_handlers[_recognizer_ptr(control)] = handler | |
| 836 | 901 | ||
| 837 | 902 | ||
@@ -1167,6 +1232,7 @@ def destroy(self, native_view: Any) -> None: | |||
| 1167 | 1232 | # Controls register their own pointer as the action-handler key | |
| 1168 | 1233 | # (see _register_control_action); drop it with the view. | |
| 1169 | 1234 | _pn_action_handlers.pop(_recognizer_ptr(native_view), None) | |
| 1235 | + _pn_last_control_event_ts.pop(_recognizer_ptr(native_view), None) | ||
| 1170 | 1236 | _pn_view_border_radius_map.pop(id(native_view), None) | |
| 1171 | 1237 | _pn_side_border_map.pop(id(native_view), None) | |
| 1172 | 1238 | try: | |
@@ -1797,6 +1863,7 @@ def _apply_refresh(self, sv: Any, props: Dict[str, Any]) -> None: | |||
| 1797 | 1863 | if existing is not None: | |
| 1798 | 1864 | existing.endRefreshing() | |
| 1799 | 1865 | _pn_action_handlers.pop(_recognizer_ptr(existing), None) | |
| 1866 | + _pn_last_control_event_ts.pop(_recognizer_ptr(existing), None) | ||
| 1800 | 1867 | sv.setRefreshControl_(None) | |
| 1801 | 1868 | sv.setAlwaysBounceVertical_(False) | |
| 1802 | 1869 | except Exception: | |
| Back | FazBrowse Home | New Git URL |
0 commit comments