From 3f033fcb90b19a1162ef050d0cfc4182051f7b4c Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Mon, 24 Aug 2026 17:59:37 +0200 Subject: [PATCH 1/2] feat: add native tap-to-focus listeners --- .../HybridTapToFocusGestureController.kt | 30 ++++++- .../HybridTapToFocusGestureController.swift | 35 ++++++++- .../generated/android/VisionCameraOnLoad.cpp | 2 + ...d__shared_ptr_HybridMeteringPointSpec_.hpp | 78 +++++++++++++++++++ ...JHybridTapToFocusGestureControllerSpec.cpp | 23 +++++- ...JHybridTapToFocusGestureControllerSpec.hpp | 3 +- ...td__shared_ptr_HybridMeteringPointSpec_.kt | 78 +++++++++++++++++++ .../HybridTapToFocusGestureControllerSpec.kt | 16 ++++ .../ios/VisionCamera-Swift-Cxx-Bridge.cpp | 8 ++ .../ios/VisionCamera-Swift-Cxx-Bridge.hpp | 22 ++++++ ...idTapToFocusGestureControllerSpecSwift.hpp | 25 +++++- ..._shared_ptr_HybridMeteringPointSpec_.swift | 50 ++++++++++++ ...ybridTapToFocusGestureControllerSpec.swift | 3 +- ...dTapToFocusGestureControllerSpec_cxx.swift | 38 +++++++++ .../HybridTapToFocusGestureControllerSpec.cpp | 3 +- .../HybridTapToFocusGestureControllerSpec.hpp | 10 ++- .../TapToFocusGestureController.nitro.ts | 25 +++++- 17 files changed, 439 insertions(+), 10 deletions(-) create mode 100644 packages/react-native-vision-camera/nitrogen/generated/android/c++/JFunc_void_std__shared_ptr_HybridMeteringPointSpec_.hpp create mode 100644 packages/react-native-vision-camera/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/Func_void_std__shared_ptr_HybridMeteringPointSpec_.kt create mode 100644 packages/react-native-vision-camera/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_HybridMeteringPointSpec_.swift diff --git a/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/gestures/HybridTapToFocusGestureController.kt b/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/gestures/HybridTapToFocusGestureController.kt index 6753f55fb6..f8dd081824 100644 --- a/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/gestures/HybridTapToFocusGestureController.kt +++ b/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/gestures/HybridTapToFocusGestureController.kt @@ -13,7 +13,9 @@ import androidx.camera.view.PreviewView import com.margelo.nitro.NitroModules import com.margelo.nitro.camera.FocusOptions import com.margelo.nitro.camera.HybridCameraControllerSpec +import com.margelo.nitro.camera.HybridMeteringPointSpec import com.margelo.nitro.camera.HybridTapToFocusGestureControllerSpec +import com.margelo.nitro.camera.ListenerSubscription import com.margelo.nitro.camera.hybrids.HybridCameraController import com.margelo.nitro.camera.hybrids.metering.HybridMeteringPoint import com.margelo.nitro.camera.public.NativeGestureController @@ -24,6 +26,8 @@ class HybridTapToFocusGestureController : override var controller: HybridCameraControllerSpec? = null private var previewView: PreviewView? = null + private val onTapListeners = mutableSetOf<(HybridMeteringPointSpec) -> Unit>() + private val onFocusCompletedListeners = mutableSetOf<(HybridMeteringPointSpec) -> Unit>() private val context: Context get() = NitroModules.applicationContext ?: throw Error("Context not available!") private var isTracking = false @@ -42,8 +46,16 @@ class HybridTapToFocusGestureController : val controller = controller ?: return false val point = previewView.meteringPointFactory.createPoint(e.x, e.y) - val meteringPoint = HybridMeteringPoint(e.x.toDouble(), e.y.toDouble(), null, point) - controller.focusTo(meteringPoint, FocusOptions(null, null, null, null)) + val density = context.resources.displayMetrics.density + val meteringPoint = HybridMeteringPoint((e.x / density).toDouble(), (e.y / density).toDouble(), null, point) + onTapListeners.toList().forEach { it(meteringPoint) } + controller + .focusTo(meteringPoint, FocusOptions(null, null, null, null)) + .then { + onFocusCompletedListeners.toList().forEach { it(meteringPoint) } + }.catch { error -> + Log.e(TAG, "Failed to focus!", error) + } return true } }, @@ -93,4 +105,18 @@ class HybridTapToFocusGestureController : } } } + + override fun addOnTapListener(onTap: (HybridMeteringPointSpec) -> Unit): ListenerSubscription { + onTapListeners.add(onTap) + return ListenerSubscription { + onTapListeners.remove(onTap) + } + } + + override fun addOnFocusCompletedListener(onFocusCompleted: (HybridMeteringPointSpec) -> Unit): ListenerSubscription { + onFocusCompletedListeners.add(onFocusCompleted) + return ListenerSubscription { + onFocusCompletedListeners.remove(onFocusCompleted) + } + } } diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/Gestures/HybridTapToFocusGestureController.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/Gestures/HybridTapToFocusGestureController.swift index 79cdcfc0fb..85665e3c71 100644 --- a/packages/react-native-vision-camera/ios/Hybrid Objects/Gestures/HybridTapToFocusGestureController.swift +++ b/packages/react-native-vision-camera/ios/Hybrid Objects/Gestures/HybridTapToFocusGestureController.swift @@ -16,6 +16,8 @@ final class HybridTapToFocusGestureController: HybridTapToFocusGestureController weak var controller: (any HybridCameraControllerSpec)? = nil private weak var previewView: (any HybridPreviewViewSpec)? = nil + private var onTapListeners: [UUID: (any HybridMeteringPointSpec) -> Void] = [:] + private var onFocusCompletedListeners: [UUID: (any HybridMeteringPointSpec) -> Void] = [:] override init() { super.init() @@ -34,12 +36,43 @@ final class HybridTapToFocusGestureController: HybridTapToFocusGestureController viewX: viewPoint.x, viewY: viewPoint.y, size: nil) - _ = try controller.focusTo(point: meteringPoint, options: FocusOptions()) + let tapListeners = Array(onTapListeners.values) + tapListeners.forEach { $0(meteringPoint) } + + try controller.focusTo(point: meteringPoint, options: FocusOptions()) + .then { [weak self] _ in + guard let self else { return } + let focusCompletedListeners = Array(self.onFocusCompletedListeners.values) + focusCompletedListeners.forEach { $0(meteringPoint) } + } + .catch { error in + logger.error("Failed to focus! \(error)") + } } catch { logger.error("Failed to focus! \(error)") } } + func addOnTapListener(onTap: @escaping (any HybridMeteringPointSpec) -> Void) + -> ListenerSubscription + { + let id = UUID() + onTapListeners[id] = onTap + return ListenerSubscription { [weak self] in + self?.onTapListeners.removeValue(forKey: id) + } + } + + func addOnFocusCompletedListener( + onFocusCompleted: @escaping (any HybridMeteringPointSpec) -> Void + ) -> ListenerSubscription { + let id = UUID() + onFocusCompletedListeners[id] = onFocusCompleted + return ListenerSubscription { [weak self] in + self?.onFocusCompletedListeners.removeValue(forKey: id) + } + } + func onAttached(to preview: any HybridPreviewViewSpec) { self.previewView = preview } diff --git a/packages/react-native-vision-camera/nitrogen/generated/android/VisionCameraOnLoad.cpp b/packages/react-native-vision-camera/nitrogen/generated/android/VisionCameraOnLoad.cpp index 9d058acd3c..fcf987e3fb 100644 --- a/packages/react-native-vision-camera/nitrogen/generated/android/VisionCameraOnLoad.cpp +++ b/packages/react-native-vision-camera/nitrogen/generated/android/VisionCameraOnLoad.cpp @@ -23,6 +23,7 @@ #include "JHybridNativeThreadFactorySpec.hpp" #include "JHybridGestureControllerSpec.hpp" #include "JHybridTapToFocusGestureControllerSpec.hpp" +#include "JFunc_void_std__shared_ptr_HybridMeteringPointSpec_.hpp" #include "JHybridZoomGestureControllerSpec.hpp" #include "JHybridCameraDeviceSpec.hpp" #include "JHybridCameraDeviceFactorySpec.hpp" @@ -131,6 +132,7 @@ void registerAllNatives() { margelo::nitro::camera::JHybridNativeThreadFactorySpec::CxxPart::registerNatives(); margelo::nitro::camera::JHybridGestureControllerSpec::CxxPart::registerNatives(); margelo::nitro::camera::JHybridTapToFocusGestureControllerSpec::CxxPart::registerNatives(); + margelo::nitro::camera::JFunc_void_std__shared_ptr_HybridMeteringPointSpec__cxx::registerNatives(); margelo::nitro::camera::JHybridZoomGestureControllerSpec::CxxPart::registerNatives(); margelo::nitro::camera::JHybridCameraDeviceSpec::CxxPart::registerNatives(); margelo::nitro::camera::JHybridCameraDeviceFactorySpec::CxxPart::registerNatives(); diff --git a/packages/react-native-vision-camera/nitrogen/generated/android/c++/JFunc_void_std__shared_ptr_HybridMeteringPointSpec_.hpp b/packages/react-native-vision-camera/nitrogen/generated/android/c++/JFunc_void_std__shared_ptr_HybridMeteringPointSpec_.hpp new file mode 100644 index 0000000000..5c08b026e5 --- /dev/null +++ b/packages/react-native-vision-camera/nitrogen/generated/android/c++/JFunc_void_std__shared_ptr_HybridMeteringPointSpec_.hpp @@ -0,0 +1,78 @@ +/// +/// JFunc_void_std__shared_ptr_HybridMeteringPointSpec_.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include + +#include +#include "HybridMeteringPointSpec.hpp" +#include +#include +#include "JHybridMeteringPointSpec.hpp" + +namespace margelo::nitro::camera { + + using namespace facebook; + + /** + * Represents the Java/Kotlin callback `(point: HybridMeteringPointSpec) -> Unit`. + * This can be passed around between C++ and Java/Kotlin. + */ + struct JFunc_void_std__shared_ptr_HybridMeteringPointSpec_: public jni::JavaClass { + public: + static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/camera/Func_void_std__shared_ptr_HybridMeteringPointSpec_;"; + + public: + /** + * Invokes the function this `JFunc_void_std__shared_ptr_HybridMeteringPointSpec_` instance holds through JNI. + */ + void invoke(const std::shared_ptr& point) const { + static const auto method = javaClassStatic()->getMethod /* point */)>("invoke"); + method(self(), std::dynamic_pointer_cast(point)->getJavaPart()); + } + }; + + /** + * An implementation of Func_void_std__shared_ptr_HybridMeteringPointSpec_ that is backed by a C++ implementation (using `std::function<...>`) + */ + class JFunc_void_std__shared_ptr_HybridMeteringPointSpec__cxx final: public jni::HybridClass { + public: + static jni::local_ref fromCpp(const std::function& /* point */)>& func) { + return JFunc_void_std__shared_ptr_HybridMeteringPointSpec__cxx::newObjectCxxArgs(func); + } + + public: + /** + * Invokes the C++ `std::function<...>` this `JFunc_void_std__shared_ptr_HybridMeteringPointSpec__cxx` instance holds. + */ + void invoke_cxx(jni::alias_ref point) { + _func(point->getJHybridMeteringPointSpec()); + } + + public: + [[nodiscard]] + inline const std::function& /* point */)>& getFunction() const { + return _func; + } + + public: + static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/camera/Func_void_std__shared_ptr_HybridMeteringPointSpec__cxx;"; + static void registerNatives() { + registerHybrid({makeNativeMethod("invoke_cxx", JFunc_void_std__shared_ptr_HybridMeteringPointSpec__cxx::invoke_cxx)}); + } + + private: + explicit JFunc_void_std__shared_ptr_HybridMeteringPointSpec__cxx(const std::function& /* point */)>& func): _func(func) { } + + private: + friend HybridBase; + std::function& /* point */)> _func; + }; + +} // namespace margelo::nitro::camera diff --git a/packages/react-native-vision-camera/nitrogen/generated/android/c++/JHybridTapToFocusGestureControllerSpec.cpp b/packages/react-native-vision-camera/nitrogen/generated/android/c++/JHybridTapToFocusGestureControllerSpec.cpp index 8cabc4a7c1..9354021e57 100644 --- a/packages/react-native-vision-camera/nitrogen/generated/android/c++/JHybridTapToFocusGestureControllerSpec.cpp +++ b/packages/react-native-vision-camera/nitrogen/generated/android/c++/JHybridTapToFocusGestureControllerSpec.cpp @@ -7,10 +7,22 @@ #include "JHybridTapToFocusGestureControllerSpec.hpp" +// Forward declaration of `ListenerSubscription` to properly resolve imports. +namespace margelo::nitro::camera { struct ListenerSubscription; } +// Forward declaration of `HybridMeteringPointSpec` to properly resolve imports. +namespace margelo::nitro::camera { class HybridMeteringPointSpec; } // Forward declaration of `HybridCameraControllerSpec` to properly resolve imports. namespace margelo::nitro::camera { class HybridCameraControllerSpec; } +#include "ListenerSubscription.hpp" +#include "JListenerSubscription.hpp" +#include +#include "JFunc_void.hpp" +#include #include +#include "HybridMeteringPointSpec.hpp" +#include "JFunc_void_std__shared_ptr_HybridMeteringPointSpec_.hpp" +#include "JHybridMeteringPointSpec.hpp" #include "HybridCameraControllerSpec.hpp" #include #include "JHybridCameraControllerSpec.hpp" @@ -56,6 +68,15 @@ namespace margelo::nitro::camera { } // Methods - + ListenerSubscription JHybridTapToFocusGestureControllerSpec::addOnTapListener(const std::function& /* point */)>& onTap) { + static const auto method = _javaPart->javaClassStatic()->getMethod(jni::alias_ref /* onTap */)>("addOnTapListener_cxx"); + auto __result = method(_javaPart, JFunc_void_std__shared_ptr_HybridMeteringPointSpec__cxx::fromCpp(onTap)); + return __result->toCpp(); + } + ListenerSubscription JHybridTapToFocusGestureControllerSpec::addOnFocusCompletedListener(const std::function& /* point */)>& onFocusCompleted) { + static const auto method = _javaPart->javaClassStatic()->getMethod(jni::alias_ref /* onFocusCompleted */)>("addOnFocusCompletedListener_cxx"); + auto __result = method(_javaPart, JFunc_void_std__shared_ptr_HybridMeteringPointSpec__cxx::fromCpp(onFocusCompleted)); + return __result->toCpp(); + } } // namespace margelo::nitro::camera diff --git a/packages/react-native-vision-camera/nitrogen/generated/android/c++/JHybridTapToFocusGestureControllerSpec.hpp b/packages/react-native-vision-camera/nitrogen/generated/android/c++/JHybridTapToFocusGestureControllerSpec.hpp index 9696e6c327..08d79ba045 100644 --- a/packages/react-native-vision-camera/nitrogen/generated/android/c++/JHybridTapToFocusGestureControllerSpec.hpp +++ b/packages/react-native-vision-camera/nitrogen/generated/android/c++/JHybridTapToFocusGestureControllerSpec.hpp @@ -57,7 +57,8 @@ namespace margelo::nitro::camera { public: // Methods - + ListenerSubscription addOnTapListener(const std::function& /* point */)>& onTap) override; + ListenerSubscription addOnFocusCompletedListener(const std::function& /* point */)>& onFocusCompleted) override; private: jni::global_ref _javaPart; diff --git a/packages/react-native-vision-camera/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/Func_void_std__shared_ptr_HybridMeteringPointSpec_.kt b/packages/react-native-vision-camera/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/Func_void_std__shared_ptr_HybridMeteringPointSpec_.kt new file mode 100644 index 0000000000..28821bf5ba --- /dev/null +++ b/packages/react-native-vision-camera/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/Func_void_std__shared_ptr_HybridMeteringPointSpec_.kt @@ -0,0 +1,78 @@ +/// +/// Func_void_std__shared_ptr_HybridMeteringPointSpec_.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.camera + +import androidx.annotation.Keep +import com.facebook.jni.HybridData +import com.facebook.proguard.annotations.DoNotStrip + + +/** + * Represents the JavaScript callback `(point: hybrid-object) => void`. + * This can be either implemented in C++ (in which case it might be a callback coming from JS), + * or in Kotlin/Java (in which case it is a native callback). + */ +@DoNotStrip +@Keep +@Suppress("ClassName", "RedundantUnitReturnType") +fun interface Func_void_std__shared_ptr_HybridMeteringPointSpec_: (HybridMeteringPointSpec) -> Unit { + /** + * Call the given JS callback. + * @throws Throwable if the JS function itself throws an error, or if the JS function/runtime has already been deleted. + */ + @DoNotStrip + @Keep + override fun invoke(point: HybridMeteringPointSpec): Unit +} + +/** + * Represents the JavaScript callback `(point: hybrid-object) => void`. + * This is implemented in C++, via a `std::function<...>`. + * The callback might be coming from JS. + */ +@DoNotStrip +@Keep +@Suppress( + "KotlinJniMissingFunction", "unused", + "RedundantSuppression", "RedundantUnitReturnType", "FunctionName", + "ConvertSecondaryConstructorToPrimary", "ClassName", "LocalVariableName", +) +class Func_void_std__shared_ptr_HybridMeteringPointSpec__cxx: Func_void_std__shared_ptr_HybridMeteringPointSpec_ { + @DoNotStrip + @Keep + private val mHybridData: HybridData + + @DoNotStrip + @Keep + private constructor(hybridData: HybridData) { + mHybridData = hybridData + } + + @DoNotStrip + @Keep + override fun invoke(point: HybridMeteringPointSpec): Unit + = invoke_cxx(point) + + private external fun invoke_cxx(point: HybridMeteringPointSpec): Unit +} + +/** + * Represents the JavaScript callback `(point: hybrid-object) => void`. + * This is implemented in Java/Kotlin, via a `(HybridMeteringPointSpec) -> Unit`. + * The callback is always coming from native. + */ +@DoNotStrip +@Keep +@Suppress("ClassName", "RedundantUnitReturnType", "unused") +class Func_void_std__shared_ptr_HybridMeteringPointSpec__java(private val function: (HybridMeteringPointSpec) -> Unit): Func_void_std__shared_ptr_HybridMeteringPointSpec_ { + @DoNotStrip + @Keep + override fun invoke(point: HybridMeteringPointSpec): Unit { + return this.function(point) + } +} diff --git a/packages/react-native-vision-camera/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/HybridTapToFocusGestureControllerSpec.kt b/packages/react-native-vision-camera/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/HybridTapToFocusGestureControllerSpec.kt index cb62373133..9e77873c44 100644 --- a/packages/react-native-vision-camera/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/HybridTapToFocusGestureControllerSpec.kt +++ b/packages/react-native-vision-camera/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/HybridTapToFocusGestureControllerSpec.kt @@ -29,7 +29,23 @@ abstract class HybridTapToFocusGestureControllerSpec: HybridGestureControllerSpe // Methods + abstract fun addOnTapListener(onTap: (point: HybridMeteringPointSpec) -> Unit): ListenerSubscription + @DoNotStrip + @Keep + private fun addOnTapListener_cxx(onTap: Func_void_std__shared_ptr_HybridMeteringPointSpec_): ListenerSubscription { + val __result = addOnTapListener(onTap) + return __result + } + + abstract fun addOnFocusCompletedListener(onFocusCompleted: (point: HybridMeteringPointSpec) -> Unit): ListenerSubscription + + @DoNotStrip + @Keep + private fun addOnFocusCompletedListener_cxx(onFocusCompleted: Func_void_std__shared_ptr_HybridMeteringPointSpec_): ListenerSubscription { + val __result = addOnFocusCompletedListener(onFocusCompleted) + return __result + } // Default implementation of `HybridObject.toString()` override fun toString(): String { diff --git a/packages/react-native-vision-camera/nitrogen/generated/ios/VisionCamera-Swift-Cxx-Bridge.cpp b/packages/react-native-vision-camera/nitrogen/generated/ios/VisionCamera-Swift-Cxx-Bridge.cpp index 307d3feee3..b4e0b38c79 100644 --- a/packages/react-native-vision-camera/nitrogen/generated/ios/VisionCamera-Swift-Cxx-Bridge.cpp +++ b/packages/react-native-vision-camera/nitrogen/generated/ios/VisionCamera-Swift-Cxx-Bridge.cpp @@ -465,6 +465,14 @@ namespace margelo::nitro::camera::bridge::swift { VisionCamera::HybridNativeThreadFactorySpec_cxx& swiftPart = swiftWrapper->getSwiftPart(); return swiftPart.toUnsafe(); } + + // pragma MARK: std::function& /* point */)> + Func_void_std__shared_ptr_HybridMeteringPointSpec_ create_Func_void_std__shared_ptr_HybridMeteringPointSpec_(void* NON_NULL swiftClosureWrapper) noexcept { + auto swiftClosure = VisionCamera::Func_void_std__shared_ptr_HybridMeteringPointSpec_::fromUnsafe(swiftClosureWrapper); + return [swiftClosure = std::move(swiftClosure)](const std::shared_ptr& point) mutable -> void { + swiftClosure.call(point); + }; + } // pragma MARK: std::function>& /* newDevices */)> Func_void_std__vector_std__shared_ptr_HybridCameraDeviceSpec__ create_Func_void_std__vector_std__shared_ptr_HybridCameraDeviceSpec__(void* NON_NULL swiftClosureWrapper) noexcept { diff --git a/packages/react-native-vision-camera/nitrogen/generated/ios/VisionCamera-Swift-Cxx-Bridge.hpp b/packages/react-native-vision-camera/nitrogen/generated/ios/VisionCamera-Swift-Cxx-Bridge.hpp index 90598bf6f0..20ea1730da 100644 --- a/packages/react-native-vision-camera/nitrogen/generated/ios/VisionCamera-Swift-Cxx-Bridge.hpp +++ b/packages/react-native-vision-camera/nitrogen/generated/ios/VisionCamera-Swift-Cxx-Bridge.hpp @@ -1325,6 +1325,28 @@ namespace margelo::nitro::camera::bridge::swift { return optional.value(); } + // pragma MARK: std::function& /* point */)> + /** + * Specialized version of `std::function&)>`. + */ + using Func_void_std__shared_ptr_HybridMeteringPointSpec_ = std::function& /* point */)>; + /** + * Wrapper class for a `std::function& / * point * /)>`, this can be used from Swift. + */ + class Func_void_std__shared_ptr_HybridMeteringPointSpec__Wrapper final { + public: + explicit Func_void_std__shared_ptr_HybridMeteringPointSpec__Wrapper(std::function& /* point */)>&& func): _function(std::make_unique& /* point */)>>(std::move(func))) {} + inline void call(std::shared_ptr point) const noexcept { + _function->operator()(point); + } + private: + std::unique_ptr& /* point */)>> _function; + } SWIFT_NONCOPYABLE; + Func_void_std__shared_ptr_HybridMeteringPointSpec_ create_Func_void_std__shared_ptr_HybridMeteringPointSpec_(void* NON_NULL swiftClosureWrapper) noexcept; + inline Func_void_std__shared_ptr_HybridMeteringPointSpec__Wrapper wrap_Func_void_std__shared_ptr_HybridMeteringPointSpec_(Func_void_std__shared_ptr_HybridMeteringPointSpec_ value) noexcept { + return Func_void_std__shared_ptr_HybridMeteringPointSpec__Wrapper(std::move(value)); + } + // pragma MARK: std::vector> /** * Specialized version of `std::vector>`. diff --git a/packages/react-native-vision-camera/nitrogen/generated/ios/c++/HybridTapToFocusGestureControllerSpecSwift.hpp b/packages/react-native-vision-camera/nitrogen/generated/ios/c++/HybridTapToFocusGestureControllerSpecSwift.hpp index 113279b07c..5a8876c483 100644 --- a/packages/react-native-vision-camera/nitrogen/generated/ios/c++/HybridTapToFocusGestureControllerSpecSwift.hpp +++ b/packages/react-native-vision-camera/nitrogen/generated/ios/c++/HybridTapToFocusGestureControllerSpecSwift.hpp @@ -12,9 +12,17 @@ // Forward declaration of `HybridTapToFocusGestureControllerSpec_cxx` to properly resolve imports. namespace VisionCamera { class HybridTapToFocusGestureControllerSpec_cxx; } +// Forward declaration of `ListenerSubscription` to properly resolve imports. +namespace margelo::nitro::camera { struct ListenerSubscription; } +// Forward declaration of `HybridMeteringPointSpec` to properly resolve imports. +namespace margelo::nitro::camera { class HybridMeteringPointSpec; } // Forward declaration of `HybridGestureControllerSpecSwift` to properly resolve imports. namespace margelo::nitro::camera { class HybridGestureControllerSpecSwift; } +#include "ListenerSubscription.hpp" +#include +#include +#include "HybridMeteringPointSpec.hpp" #include "HybridGestureControllerSpecSwift.hpp" #include "VisionCamera-Swift-Cxx-Umbrella.hpp" @@ -68,7 +76,22 @@ namespace margelo::nitro::camera { public: // Methods - + inline ListenerSubscription addOnTapListener(const std::function& /* point */)>& onTap) override { + auto __result = _swiftPart.addOnTapListener(onTap); + if (__result.hasError()) [[unlikely]] { + std::rethrow_exception(__result.error()); + } + auto __value = std::move(__result.value()); + return __value; + } + inline ListenerSubscription addOnFocusCompletedListener(const std::function& /* point */)>& onFocusCompleted) override { + auto __result = _swiftPart.addOnFocusCompletedListener(onFocusCompleted); + if (__result.hasError()) [[unlikely]] { + std::rethrow_exception(__result.error()); + } + auto __value = std::move(__result.value()); + return __value; + } private: VisionCamera::HybridTapToFocusGestureControllerSpec_cxx _swiftPart; diff --git a/packages/react-native-vision-camera/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_HybridMeteringPointSpec_.swift b/packages/react-native-vision-camera/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_HybridMeteringPointSpec_.swift new file mode 100644 index 0000000000..6578d12185 --- /dev/null +++ b/packages/react-native-vision-camera/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_HybridMeteringPointSpec_.swift @@ -0,0 +1,50 @@ +/// +/// Func_void_std__shared_ptr_HybridMeteringPointSpec_.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +import NitroModules + +/** + * Wraps a Swift `(_ point: (any HybridMeteringPointSpec)) -> Void` as a class. + * This class can be used from C++, e.g. to wrap the Swift closure as a `std::function`. + */ +public final class Func_void_std__shared_ptr_HybridMeteringPointSpec_ { + public typealias bridge = margelo.nitro.camera.bridge.swift + + private let closure: (_ point: (any HybridMeteringPointSpec)) -> Void + + public init(_ closure: @escaping (_ point: (any HybridMeteringPointSpec)) -> Void) { + self.closure = closure + } + + @inline(__always) + public func call(point: bridge.std__shared_ptr_HybridMeteringPointSpec_) -> Void { + self.closure({ () -> any HybridMeteringPointSpec in + let __unsafePointer = bridge.get_std__shared_ptr_HybridMeteringPointSpec_(point) + let __instance = HybridMeteringPointSpec_cxx.fromUnsafe(__unsafePointer) + return __instance.getHybridMeteringPointSpec() + }()) + } + + /** + * Casts this instance to a retained unsafe raw pointer. + * This acquires one additional strong reference on the object! + */ + @inline(__always) + public func toUnsafe() -> UnsafeMutableRawPointer { + return Unmanaged.passRetained(self).toOpaque() + } + + /** + * Casts an unsafe pointer to a `Func_void_std__shared_ptr_HybridMeteringPointSpec_`. + * The pointer has to be a retained opaque `Unmanaged`. + * This removes one strong reference from the object! + */ + @inline(__always) + public static func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> Func_void_std__shared_ptr_HybridMeteringPointSpec_ { + return Unmanaged.fromOpaque(pointer).takeRetainedValue() + } +} diff --git a/packages/react-native-vision-camera/nitrogen/generated/ios/swift/HybridTapToFocusGestureControllerSpec.swift b/packages/react-native-vision-camera/nitrogen/generated/ios/swift/HybridTapToFocusGestureControllerSpec.swift index 07120b8a53..8718076a3d 100644 --- a/packages/react-native-vision-camera/nitrogen/generated/ios/swift/HybridTapToFocusGestureControllerSpec.swift +++ b/packages/react-native-vision-camera/nitrogen/generated/ios/swift/HybridTapToFocusGestureControllerSpec.swift @@ -13,7 +13,8 @@ public protocol HybridTapToFocusGestureControllerSpec_protocol: HybridObject, Hy // Methods - + func addOnTapListener(onTap: @escaping (_ point: (any HybridMeteringPointSpec)) -> Void) throws -> ListenerSubscription + func addOnFocusCompletedListener(onFocusCompleted: @escaping (_ point: (any HybridMeteringPointSpec)) -> Void) throws -> ListenerSubscription } public extension HybridTapToFocusGestureControllerSpec_protocol { diff --git a/packages/react-native-vision-camera/nitrogen/generated/ios/swift/HybridTapToFocusGestureControllerSpec_cxx.swift b/packages/react-native-vision-camera/nitrogen/generated/ios/swift/HybridTapToFocusGestureControllerSpec_cxx.swift index be3ea744d2..d1182de240 100644 --- a/packages/react-native-vision-camera/nitrogen/generated/ios/swift/HybridTapToFocusGestureControllerSpec_cxx.swift +++ b/packages/react-native-vision-camera/nitrogen/generated/ios/swift/HybridTapToFocusGestureControllerSpec_cxx.swift @@ -127,5 +127,43 @@ open class HybridTapToFocusGestureControllerSpec_cxx : HybridGestureControllerSp // Methods + @inline(__always) + public final func addOnTapListener(onTap: bridge.Func_void_std__shared_ptr_HybridMeteringPointSpec_) -> bridge.Result_ListenerSubscription_ { + do { + let __result = try self.__implementation.addOnTapListener(onTap: { () -> ((any HybridMeteringPointSpec)) -> Void in + let __wrappedFunction = bridge.wrap_Func_void_std__shared_ptr_HybridMeteringPointSpec_(onTap) + return { (__point: (any HybridMeteringPointSpec)) -> Void in + __wrappedFunction.call({ () -> bridge.std__shared_ptr_HybridMeteringPointSpec_ in + let __cxxWrapped = __point.getCxxWrapper() + return __cxxWrapped.getCxxPart() + }()) + } + }()) + let __resultCpp = __result + return bridge.create_Result_ListenerSubscription_(__resultCpp) + } catch (let __error) { + let __exceptionPtr = __error.toCpp() + return bridge.create_Result_ListenerSubscription_(__exceptionPtr) + } + } + @inline(__always) + public final func addOnFocusCompletedListener(onFocusCompleted: bridge.Func_void_std__shared_ptr_HybridMeteringPointSpec_) -> bridge.Result_ListenerSubscription_ { + do { + let __result = try self.__implementation.addOnFocusCompletedListener(onFocusCompleted: { () -> ((any HybridMeteringPointSpec)) -> Void in + let __wrappedFunction = bridge.wrap_Func_void_std__shared_ptr_HybridMeteringPointSpec_(onFocusCompleted) + return { (__point: (any HybridMeteringPointSpec)) -> Void in + __wrappedFunction.call({ () -> bridge.std__shared_ptr_HybridMeteringPointSpec_ in + let __cxxWrapped = __point.getCxxWrapper() + return __cxxWrapped.getCxxPart() + }()) + } + }()) + let __resultCpp = __result + return bridge.create_Result_ListenerSubscription_(__resultCpp) + } catch (let __error) { + let __exceptionPtr = __error.toCpp() + return bridge.create_Result_ListenerSubscription_(__exceptionPtr) + } + } } diff --git a/packages/react-native-vision-camera/nitrogen/generated/shared/c++/HybridTapToFocusGestureControllerSpec.cpp b/packages/react-native-vision-camera/nitrogen/generated/shared/c++/HybridTapToFocusGestureControllerSpec.cpp index bb9993ef3c..133912f676 100644 --- a/packages/react-native-vision-camera/nitrogen/generated/shared/c++/HybridTapToFocusGestureControllerSpec.cpp +++ b/packages/react-native-vision-camera/nitrogen/generated/shared/c++/HybridTapToFocusGestureControllerSpec.cpp @@ -15,7 +15,8 @@ namespace margelo::nitro::camera { HybridGestureControllerSpec::loadHybridMethods(); // load custom methods/properties registerHybrids(this, [](Prototype& prototype) { - + prototype.registerHybridMethod("addOnTapListener", &HybridTapToFocusGestureControllerSpec::addOnTapListener); + prototype.registerHybridMethod("addOnFocusCompletedListener", &HybridTapToFocusGestureControllerSpec::addOnFocusCompletedListener); }); } diff --git a/packages/react-native-vision-camera/nitrogen/generated/shared/c++/HybridTapToFocusGestureControllerSpec.hpp b/packages/react-native-vision-camera/nitrogen/generated/shared/c++/HybridTapToFocusGestureControllerSpec.hpp index 25c48cc51a..06c0e259ca 100644 --- a/packages/react-native-vision-camera/nitrogen/generated/shared/c++/HybridTapToFocusGestureControllerSpec.hpp +++ b/packages/react-native-vision-camera/nitrogen/generated/shared/c++/HybridTapToFocusGestureControllerSpec.hpp @@ -13,10 +13,17 @@ #error NitroModules cannot be found! Are you sure you installed NitroModules properly? #endif +// Forward declaration of `ListenerSubscription` to properly resolve imports. +namespace margelo::nitro::camera { struct ListenerSubscription; } +// Forward declaration of `HybridMeteringPointSpec` to properly resolve imports. +namespace margelo::nitro::camera { class HybridMeteringPointSpec; } // Forward declaration of `HybridGestureControllerSpec` to properly resolve imports. namespace margelo::nitro::camera { class HybridGestureControllerSpec; } +#include "ListenerSubscription.hpp" #include +#include "HybridMeteringPointSpec.hpp" +#include #include "HybridGestureControllerSpec.hpp" namespace margelo::nitro::camera { @@ -50,7 +57,8 @@ namespace margelo::nitro::camera { public: // Methods - + virtual ListenerSubscription addOnTapListener(const std::function& /* point */)>& onTap) = 0; + virtual ListenerSubscription addOnFocusCompletedListener(const std::function& /* point */)>& onFocusCompleted) = 0; protected: // Hybrid Setup diff --git a/packages/react-native-vision-camera/src/specs/gestures/TapToFocusGestureController.nitro.ts b/packages/react-native-vision-camera/src/specs/gestures/TapToFocusGestureController.nitro.ts index 274dbb9c9b..830cd78dca 100644 --- a/packages/react-native-vision-camera/src/specs/gestures/TapToFocusGestureController.nitro.ts +++ b/packages/react-native-vision-camera/src/specs/gestures/TapToFocusGestureController.nitro.ts @@ -1,4 +1,6 @@ import type { CameraController } from '../CameraController.nitro' +import type { ListenerSubscription } from '../common-types/ListenerSubscription' +import type { MeteringPoint } from '../metering/MeteringPoint.nitro' import type { GestureController } from './GestureController.nitro' /** @@ -7,4 +9,25 @@ import type { GestureController } from './GestureController.nitro' * {@linkcode CameraController.focusTo | focusTo(...)} * action via a native tap to focus gesture. */ -export interface TapToFocusGestureController extends GestureController {} +export interface TapToFocusGestureController extends GestureController { + /** + * Adds a listener that is called when a native tap gesture creates a + * {@linkcode MeteringPoint}, immediately before focusing begins. + * + * Call {@linkcode ListenerSubscription.remove | remove()} on the returned + * subscription to stop receiving updates. + */ + addOnTapListener(onTap: (point: MeteringPoint) => void): ListenerSubscription + + /** + * Adds a listener that is called when the focus operation triggered by a + * native tap completes. + * + * This is not called if the focus operation fails. Call + * {@linkcode ListenerSubscription.remove | remove()} on the returned + * subscription to stop receiving updates. + */ + addOnFocusCompletedListener( + onFocusCompleted: (point: MeteringPoint) => void, + ): ListenerSubscription +} From 709bdc452a9fd164dcdddefa6fbf8ef1feea41fd Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Mon, 24 Aug 2026 18:03:43 +0200 Subject: [PATCH 2/2] fix: deliver focus gesture events on main thread --- .../hybrids/gestures/HybridTapToFocusGestureController.kt | 7 +++++-- .../Gestures/HybridTapToFocusGestureController.swift | 8 +++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/gestures/HybridTapToFocusGestureController.kt b/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/gestures/HybridTapToFocusGestureController.kt index f8dd081824..2fc119c055 100644 --- a/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/gestures/HybridTapToFocusGestureController.kt +++ b/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/gestures/HybridTapToFocusGestureController.kt @@ -30,6 +30,7 @@ class HybridTapToFocusGestureController : private val onFocusCompletedListeners = mutableSetOf<(HybridMeteringPointSpec) -> Unit>() private val context: Context get() = NitroModules.applicationContext ?: throw Error("Context not available!") + private val mainHandler = Handler(Looper.getMainLooper()) private var isTracking = false private val gestureDetector = GestureDetector( @@ -52,14 +53,16 @@ class HybridTapToFocusGestureController : controller .focusTo(meteringPoint, FocusOptions(null, null, null, null)) .then { - onFocusCompletedListeners.toList().forEach { it(meteringPoint) } + mainHandler.post { + onFocusCompletedListeners.toList().forEach { it(meteringPoint) } + } }.catch { error -> Log.e(TAG, "Failed to focus!", error) } return true } }, - Handler(Looper.getMainLooper()), + mainHandler, ) override fun onTouchEvent( diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/Gestures/HybridTapToFocusGestureController.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/Gestures/HybridTapToFocusGestureController.swift index 85665e3c71..db80e63b02 100644 --- a/packages/react-native-vision-camera/ios/Hybrid Objects/Gestures/HybridTapToFocusGestureController.swift +++ b/packages/react-native-vision-camera/ios/Hybrid Objects/Gestures/HybridTapToFocusGestureController.swift @@ -41,9 +41,11 @@ final class HybridTapToFocusGestureController: HybridTapToFocusGestureController try controller.focusTo(point: meteringPoint, options: FocusOptions()) .then { [weak self] _ in - guard let self else { return } - let focusCompletedListeners = Array(self.onFocusCompletedListeners.values) - focusCompletedListeners.forEach { $0(meteringPoint) } + DispatchQueue.main.async { [weak self] in + guard let self else { return } + let focusCompletedListeners = Array(self.onFocusCompletedListeners.values) + focusCompletedListeners.forEach { $0(meteringPoint) } + } } .catch { error in logger.error("Failed to focus! \(error)")