Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
package com.rnmapbox.rnmbx.components.camera

import com.facebook.react.bridge.Callback
import android.view.animation.AccelerateDecelerateInterpolator
import android.view.animation.LinearInterpolator
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.WritableNativeMap
import com.mapbox.maps.ScreenCoordinate
import com.mapbox.maps.plugin.animation.MapAnimationOptions
import com.mapbox.maps.plugin.animation.easeTo
import com.mapbox.maps.plugin.animation.moveBy
import com.mapbox.maps.plugin.animation.scaleBy
import com.mapbox.maps.toCameraOptions
import com.rnmapbox.rnmbx.NativeRNMBXCameraModuleSpec
import com.rnmapbox.rnmbx.components.camera.constants.CameraMode
import com.rnmapbox.rnmbx.components.mapview.CommandResponse
import com.rnmapbox.rnmbx.utils.ViewRefTag
import com.rnmapbox.rnmbx.utils.ViewTagResolver
Expand Down Expand Up @@ -49,4 +56,60 @@ class RNMBXCameraModule(context: ReactApplicationContext, val viewTagResolver: V
promise.resolve(null)
}
}

private fun getAnimationOptions(
animationMode: Double,
animationDuration: Double
): MapAnimationOptions {
return MapAnimationOptions.Builder()
.apply {
when (animationMode.toInt()) {
CameraMode.LINEAR -> interpolator(LinearInterpolator())
CameraMode.EASE -> interpolator(AccelerateDecelerateInterpolator())
}
animationDuration.let { duration ->
duration(duration.toLong())
}
}
.build()
}

override fun moveBy(
viewRef: ViewRefTag?,
x: Double,
y: Double,
animationMode: Double,
animationDuration: Double,
promise: Promise
) {
withViewportOnUIThread(viewRef, promise) {
it.mapboxMap?.let { map ->
val animationOptions = getAnimationOptions(animationMode, animationDuration)
map.moveBy(ScreenCoordinate(x, y), animationOptions)

promise.resolve(null)
}
}
}

override fun scaleBy(
viewRef: ViewRefTag?,
x: Double,
y: Double,
animationMode: Double,
animationDuration: Double,
scaleFactor: Double,
promise: Promise
) {
withViewportOnUIThread(viewRef, promise) {
it.mapboxMap?.let { map ->
val animationOptions =
getAnimationOptions(animationMode, animationDuration)

map.scaleBy(scaleFactor, ScreenCoordinate(x, y), animationOptions)

promise.resolve(null)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,12 @@ public NativeRNMBXCameraModuleSpec(ReactApplicationContext reactContext) {
@ReactMethod
@DoNotStrip
public abstract void updateCameraStop(@Nullable Double viewRef, ReadableMap stop, Promise promise);

@ReactMethod
@DoNotStrip
public abstract void moveBy(@Nullable Double viewRef, double x, double y, double animationMode, double animationDuration, Promise promise);

@ReactMethod
@DoNotStrip
public abstract void scaleBy(@Nullable Double viewRef, double x, double y, double animationMode, double animationDuration, double scaleFactor, Promise promise);
}
22 changes: 21 additions & 1 deletion docs/Camera.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,4 +347,24 @@ camera.zoomTo(16, 100);
```


[Fit](../examples/Camera/Fit)
[Fit](../examples/Camera/Fit)### moveBy()

Move the map by a given screen coordinate offset with optional animation.<br/>Can be used to get the Android Auto (onScroll) or Carplay(mapTemplate didUpdatePanGestureWithTranslation) pan gesture applied, for these to work properly do not specify animationDuration.

#### arguments
| Name | Type | Required | Description |
| ---- | :--: | :------: | :----------: |



### scaleBy()

Scale the map with optional animation.<br/>Can be used to get Android Auto pinch gesture (onScale with scaleFactor > 0.0 and < 2.0) or Android Auto double tap (onScale with scaleFactor == 2.0) applied.

#### arguments
| Name | Type | Required | Description |
| ---- | :--: | :------: | :----------: |




18 changes: 18 additions & 0 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,24 @@
"examples": [
"\ncamera.zoomTo(16);\ncamera.zoomTo(16, 100);\n\n"
]
},
{
"name": "moveBy",
"docblock": "Move the map by a given screen coordinate offset with optional animation.\nCan be used to get the Android Auto (onScroll) or Carplay(mapTemplate didUpdatePanGestureWithTranslation) pan gesture applied, for these to work properly do not specify animationDuration.\n\n@param {number} x screen coordinate offset\n@param {number} y screen coordinate offset\n@param {NativeAnimationMode} animationMode mode used for the animation\n@param {number} animationDuration The transition duration\n@param {number} scaleFactor scale factor value > 0.0 and < 2.0 when 1.0 means no scaling, > 1.0 zoom in and < 1.0 zoom out",
"modifiers": [],
"params": [],
"returns": null,
"description": "Move the map by a given screen coordinate offset with optional animation.\nCan be used to get the Android Auto (onScroll) or Carplay(mapTemplate didUpdatePanGestureWithTranslation) pan gesture applied, for these to work properly do not specify animationDuration.",
"examples": []
},
{
"name": "scaleBy",
"docblock": "Scale the map with optional animation.\nCan be used to get Android Auto pinch gesture (onScale with scaleFactor > 0.0 and < 2.0) or Android Auto double tap (onScale with scaleFactor == 2.0) applied.\n\n@param {number} x center screen coordinate\n@param {number} y center screen coordinate\n@param {number} scaleFactor scale factor value > 0.0 and < 2.0 when 1.0 means no scaling, > 1.0 zoom in and < 1.0 zoom out\n@param {NativeAnimationMode} animationMode mode used for the animation\n@param {number} animationDuration The transition duration",
"modifiers": [],
"params": [],
"returns": null,
"description": "Scale the map with optional animation.\nCan be used to get Android Auto pinch gesture (onScale with scaleFactor > 0.0 and < 2.0) or Android Auto double tap (onScale with scaleFactor == 2.0) applied.",
"examples": []
}
],
"props": [
Expand Down
67 changes: 64 additions & 3 deletions ios/RNMBX/RNMBXCamera.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ public protocol RNMBXMapComponent: AnyObject {
func waitForStyleLoad() -> Bool
}

enum CameraMode: String, CaseIterable {
case flight, ease, linear, none
enum CameraMode: Int {
case flight = 1
case ease = 2
case linear = 3
case move = 4
case none = 5
}

enum UserTrackingMode: String {
Expand Down Expand Up @@ -477,7 +481,7 @@ open class RNMBXCamera : RNMBXMapComponentBase {
}

var mode: CameraMode = .flight
if let m = stop["mode"] as? String, let m = CameraMode(rawValue: m) {
if let m = stop["mode"] as? NSNumber, let m = CameraMode(rawValue: m.intValue) {
mode = m
}

Expand Down Expand Up @@ -528,6 +532,63 @@ open class RNMBXCamera : RNMBXMapComponentBase {
map.mapView.viewport.removeStatusObserver(self)
return super.removeFromMap(map, reason:reason)
}

@objc public func moveBy(x: Double, y: Double, animationMode: NSNumber?, animationDuration: NSNumber?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
withMapView { mapView in
let contentFrame = mapView.bounds.inset(by: mapView.safeAreaInsets)
let centerPoint = CGPoint(x: contentFrame.midX, y: contentFrame.midY)
let endCameraPoint = CGPoint(x: centerPoint.x + x, y: centerPoint.y + y)
let cameraOptions = mapView.mapboxMap.dragCameraOptions(from: centerPoint, to: endCameraPoint)

let duration = (animationDuration?.doubleValue ?? 0.0) / 1000

if (duration == 0.0) {
mapView.mapboxMap.setCamera(to: cameraOptions)
resolve(nil)
return
}

var curve: UIView.AnimationCurve = .linear
if let m = animationMode?.intValue, let m = CameraMode(rawValue: m) {
curve = m == CameraMode.ease ? .easeInOut : .linear
}

mapView.camera.ease(to: cameraOptions, duration: duration, curve: curve, completion: { _ in resolve(nil) })
}
}

@objc public func scaleBy(
x: Double,
y: Double,
scaleFactor: NSNumber,
animationMode: NSNumber?,
animationDuration: NSNumber?,
resolve: @escaping RCTPromiseResolveBlock,
reject: @escaping RCTPromiseRejectBlock
) {
withMapView { mapView in
let currentZoom = mapView.cameraState.zoom
let newZoom = currentZoom + log2(scaleFactor.doubleValue)
let anchor = CGPoint(x: x, y: y)
let cameraOptions = CameraOptions(anchor: anchor, zoom: newZoom)
let duration = (animationDuration?.doubleValue ?? 0.0) / 1000

if (duration == 0.0) {
mapView.mapboxMap.setCamera(to: cameraOptions)
resolve(nil)
return
}

var curve: UIView.AnimationCurve = .linear
if let m = animationMode?.intValue, let m = CameraMode(rawValue: m) {
curve = m == CameraMode.ease ? .easeInOut : .linear
}

mapView.camera.ease(to: cameraOptions, duration: duration, curve: curve) { _ in
resolve(nil)
}
}
}
}

// MARK: - ViewportStatusObserver
Expand Down
27 changes: 26 additions & 1 deletion ios/RNMBX/RNMBXCameraModule.mm
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,32 @@ - (void)withCamera:(nonnull NSNumber*)viewRef block:(void (^)(RNMBXCamera *))blo
[self withCamera:viewRef block:^(RNMBXCamera *view) {
[view updateCameraStop: stop];
resolve(@true);
} reject:reject methodName:@"someMethod"];
} reject:reject methodName:@"updateCameraStop"];
}

RCT_EXPORT_METHOD(moveBy:(nonnull NSNumber *)viewRef
x:(double)x
y:(double)y
animationMode:(nonnull NSNumber *)animationMode
animationDuration:(nonnull NSNumber *)animationDuration
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject) {
[self withCamera:viewRef block:^(RNMBXCamera *camera) {
[camera moveByX:x y:y animationMode:animationMode animationDuration:animationDuration resolve:resolve reject:reject];
} reject:reject methodName:@"moveBy"];
}

RCT_EXPORT_METHOD(scaleBy:(nonnull NSNumber *)viewRef
x:(double)x
y:(double)y
animationMode:(nonnull NSNumber *)animationMode
animationDuration:(nonnull NSNumber *)animationDuration
scaleFactor:(nonnull NSNumber *)scaleFactor
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject) {
[self withCamera:viewRef block:^(RNMBXCamera *camera) {
[camera scaleByX:x y:y scaleFactor:scaleFactor animationMode:animationMode animationDuration:animationDuration resolve:resolve reject:reject];
} reject:reject methodName:@"scaleBy"];
}

@end
84 changes: 83 additions & 1 deletion src/components/Camera.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,13 @@ const nativeAnimationMode = (

// Native module types.

type NativeAnimationMode = 'flight' | 'ease' | 'linear' | 'none' | 'move';
type FLIGHT = 1;
type EASE = 2;
type LINEAR = 3;
type MOVE = 4;
type NONE = 5;

type NativeAnimationMode = FLIGHT | EASE | LINEAR | MOVE | NONE;

interface NativeCameraProps extends CameraFollowConfig {
testID?: string;
Expand Down Expand Up @@ -100,6 +106,31 @@ export interface CameraRef {
flyTo: (centerCoordinate: Position, animationDuration?: number) => void;
moveTo: (centerCoordinate: Position, animationDuration?: number) => void;
zoomTo: (zoomLevel: number, animationDuration?: number) => void;
moveBy: (
props:
| { x: number; y: number }
| {
x: number;
y: number;
animationMode: 'easeTo' | 'linearTo';
animationDuration: number;
},
) => void;
scaleBy: (
props:
| {
x: number;
y: number;
scaleFactor: number;
}
| {
x: number;
y: number;
scaleFactor: number;
animationMode: 'easeTo' | 'linearTo';
animationDuration: number;
},
) => void;
}

export type CameraStop = {
Expand Down Expand Up @@ -520,6 +551,35 @@ export const Camera = memo(
};
const zoomTo = useCallback(_zoomTo, [setCamera]);

const moveBy: CameraRef['moveBy'] = useCallback(
(
moveProps,
) => {
commands.call<void>('moveBy', [
moveProps.x,
moveProps.y,
'animationMode' in moveProps ? nativeAnimationMode(moveProps.animationMode) : nativeAnimationMode('linearTo'),
'animationDuration' in moveProps ? moveProps.animationDuration : 0,
]);
},
[commands],
);

const scaleBy: CameraRef['scaleBy'] = useCallback(
(
scaleProps
) => {
commands.call<void>('scaleBy', [
scaleProps.x,
scaleProps.y,
'animationMode' in scaleProps ? nativeAnimationMode(scaleProps.animationMode) : nativeAnimationMode('linearTo'),
'animationDuration' in scaleProps ? scaleProps.animationDuration : 0,
scaleProps.scaleFactor,
]);
},
[commands],
);

useImperativeHandle(ref, () => ({
/**
* Sets any camera properties, with default fallbacks if unspecified.
Expand Down Expand Up @@ -580,6 +640,28 @@ export const Camera = memo(
* @param {number} animationDuration The transition duration
*/
zoomTo,
/**
* Move the map by a given screen coordinate offset with optional animation.
* Can be used to get the Android Auto (onScroll) or Carplay(mapTemplate didUpdatePanGestureWithTranslation) pan gesture applied, for these to work properly do not specify animationDuration.
*
* @param {number} x screen coordinate offset
* @param {number} y screen coordinate offset
* @param {NativeAnimationMode} animationMode mode used for the animation
* @param {number} animationDuration The transition duration
* @param {number} scaleFactor scale factor value > 0.0 and < 2.0 when 1.0 means no scaling, > 1.0 zoom in and < 1.0 zoom out
*/
moveBy,
/**
* Scale the map with optional animation.
* Can be used to get Android Auto pinch gesture (onScale with scaleFactor > 0.0 and < 2.0) or Android Auto double tap (onScale with scaleFactor == 2.0) applied.
*
* @param {number} x center screen coordinate
* @param {number} y center screen coordinate
* @param {number} scaleFactor scale factor value > 0.0 and < 2.0 when 1.0 means no scaling, > 1.0 zoom in and < 1.0 zoom out
* @param {NativeAnimationMode} animationMode mode used for the animation
* @param {number} animationDuration The transition duration
*/
scaleBy,
}));

return (
Expand Down
Loading
Loading