From b5f7d3bd4d9f78db4ba50fad4b136d9bed2d1b71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Fazekas?= Date: Sun, 22 Mar 2026 16:08:52 +0100 Subject: [PATCH 1/9] feat: add Snow and Rain particle effect components Implements and components backed by Mapbox Maps SDK v11.9+ experimental precipitation APIs (@_spi(Experimental)). - Add snow/rain to the code generator (generateCodeWithEjs.mjs, globals.mjs) - Generate SnowLayerStyleProps / RainLayerStyleProps TypeScript types - Add RNMBXSnow / RNMBXRain iOS singleton layer implementations - Add RNMBXSnow / RNMBXRain Fabric component views (iOS) - Add RNMBXSnow / RNMBXRain Android implementations + managers - Register components in RNMBXPackage and package.json codegenConfig - Export Snow and Rain from src/Mapbox.native.ts - Add SnowAndRain example (V11) and integrate into TerrainSkyAtmosphere - Bump Mapbox SDK iOS + Android to 11.20.1 - Work around MapContentReconciler reset of rain by async re-applying after initial style load completes --- .../java/com/rnmapbox/rnmbx/RNMBXPackage.kt | 4 + .../components/styles/RNMBXStyleFactory.kt | 608 ++++++++++++++++++ .../rnmbx/components/styles/rain/RNMBXRain.kt | 66 ++ .../styles/rain/RNMBXRainManager.kt | 40 ++ .../rnmbx/components/styles/snow/RNMBXSnow.kt | 66 ++ .../styles/snow/RNMBXSnowManager.kt | 40 ++ docs/Rain.md | 30 + docs/Snow.md | 30 + docs/docs.json | 34 + .../src/examples/V10/TerrainSkyAtmosphere.tsx | 74 ++- example/src/examples/V11/SnowAndRain.tsx | 75 +++ example/src/scenes/GroupAndItem.tsx | 2 + ios/RNMBX/RNMBXRain.swift | 83 +++ ios/RNMBX/RNMBXRainComponentView.h | 14 + ios/RNMBX/RNMBXRainComponentView.mm | 79 +++ ios/RNMBX/RNMBXSnow.swift | 76 +++ ios/RNMBX/RNMBXSnowComponentView.h | 14 + ios/RNMBX/RNMBXSnowComponentView.mm | 79 +++ ios/RNMBX/RNMBXStyle.swift | 388 +++++++++++ package.json | 12 +- plugin/install.md | 4 +- .../autogenHelpers/generateCodeWithEjs.mjs | 20 + scripts/autogenHelpers/globals.mjs | 4 + scripts/templates/RNMBXStyleFactoryv10.kt.ejs | 2 + src/Mapbox.native.ts | 2 + src/components/Rain.tsx | 22 + src/components/Snow.tsx | 22 + src/specs/RNMBXRainNativeComponent.ts | 13 + src/specs/RNMBXSnowNativeComponent.ts | 13 + src/utils/MapboxStyles.ts | 177 +++++ src/utils/styleMap.ts | 36 ++ 31 files changed, 2100 insertions(+), 29 deletions(-) create mode 100644 android/src/main/java/com/rnmapbox/rnmbx/components/styles/rain/RNMBXRain.kt create mode 100644 android/src/main/java/com/rnmapbox/rnmbx/components/styles/rain/RNMBXRainManager.kt create mode 100644 android/src/main/java/com/rnmapbox/rnmbx/components/styles/snow/RNMBXSnow.kt create mode 100644 android/src/main/java/com/rnmapbox/rnmbx/components/styles/snow/RNMBXSnowManager.kt create mode 100644 docs/Rain.md create mode 100644 docs/Snow.md create mode 100644 example/src/examples/V11/SnowAndRain.tsx create mode 100644 ios/RNMBX/RNMBXRain.swift create mode 100644 ios/RNMBX/RNMBXRainComponentView.h create mode 100644 ios/RNMBX/RNMBXRainComponentView.mm create mode 100644 ios/RNMBX/RNMBXSnow.swift create mode 100644 ios/RNMBX/RNMBXSnowComponentView.h create mode 100644 ios/RNMBX/RNMBXSnowComponentView.mm create mode 100644 src/components/Rain.tsx create mode 100644 src/components/Snow.tsx create mode 100644 src/specs/RNMBXRainNativeComponent.ts create mode 100644 src/specs/RNMBXSnowNativeComponent.ts diff --git a/android/src/main/java/com/rnmapbox/rnmbx/RNMBXPackage.kt b/android/src/main/java/com/rnmapbox/rnmbx/RNMBXPackage.kt index b86218ce8..f82fadd32 100644 --- a/android/src/main/java/com/rnmapbox/rnmbx/RNMBXPackage.kt +++ b/android/src/main/java/com/rnmapbox/rnmbx/RNMBXPackage.kt @@ -26,6 +26,8 @@ import com.rnmapbox.rnmbx.components.mapview.NativeMapViewModule import com.rnmapbox.rnmbx.components.mapview.RNMBXMapViewManager import com.rnmapbox.rnmbx.components.styles.RNMBXStyleImportManager import com.rnmapbox.rnmbx.components.styles.atmosphere.RNMBXAtmosphereManager +import com.rnmapbox.rnmbx.components.styles.snow.RNMBXSnowManager +import com.rnmapbox.rnmbx.components.styles.rain.RNMBXRainManager import com.rnmapbox.rnmbx.components.styles.layers.RNMBXBackgroundLayerManager import com.rnmapbox.rnmbx.components.styles.layers.RNMBXCircleLayerManager import com.rnmapbox.rnmbx.components.styles.layers.RNMBXFillExtrusionLayerManager @@ -169,6 +171,8 @@ class RNMBXPackage : TurboReactPackage() { managers.add(RNMBXSkyLayerManager()) managers.add(RNMBXTerrainManager()) managers.add(RNMBXAtmosphereManager()) + managers.add(RNMBXSnowManager()) + managers.add(RNMBXRainManager()) managers.add(RNMBXBackgroundLayerManager()) managers.add(RNMBXLightManager()) managers.add(RNMBXModelLayerManager()) diff --git a/android/src/main/java/com/rnmapbox/rnmbx/components/styles/RNMBXStyleFactory.kt b/android/src/main/java/com/rnmapbox/rnmbx/components/styles/RNMBXStyleFactory.kt index 0fd2d91a2..bcd5c8943 100644 --- a/android/src/main/java/com/rnmapbox/rnmbx/components/styles/RNMBXStyleFactory.kt +++ b/android/src/main/java/com/rnmapbox/rnmbx/components/styles/RNMBXStyleFactory.kt @@ -18,6 +18,8 @@ import com.mapbox.maps.extension.style.layers.generated.SymbolLayer import com.mapbox.maps.extension.style.layers.generated.HeatmapLayer import com.mapbox.maps.extension.style.layers.generated.HillshadeLayer import com.mapbox.maps.extension.style.atmosphere.generated.Atmosphere +import com.mapbox.maps.extension.style.snow.generated.Snow +import com.mapbox.maps.extension.style.rain.generated.Rain import com.mapbox.maps.extension.style.terrain.generated.Terrain import com.mapbox.maps.extension.style.layers.generated.ModelLayer // import com.mapbox.maps.extension.style.layers.properties.generated.Visibility @@ -1027,6 +1029,118 @@ object RNMBXStyleFactory { } } } + fun setSnowLayerStyle(layer: Snow, style: RNMBXStyle ) { + val styleKeys = style.allStyleKeys + + if (styleKeys.isEmpty()) { + return + } + + for (styleKey in styleKeys) { + try { + val styleValue = style.getStyleValueForKey(styleKey) + + when (styleKey) { + "density" -> + setDensity(layer, styleValue) + "densityTransition" -> + setDensityTransition(layer, styleValue) + "intensity" -> + setIntensity(layer, styleValue) + "intensityTransition" -> + setIntensityTransition(layer, styleValue) + "color" -> + setColor(layer, styleValue) + "colorTransition" -> + setColorTransition(layer, styleValue) + "opacity" -> + setOpacity(layer, styleValue) + "opacityTransition" -> + setOpacityTransition(layer, styleValue) + "vignette" -> + setVignette(layer, styleValue) + "vignetteTransition" -> + setVignetteTransition(layer, styleValue) + "vignetteColor" -> + setVignetteColor(layer, styleValue) + "vignetteColorTransition" -> + setVignetteColorTransition(layer, styleValue) + "centerThinning" -> + setCenterThinning(layer, styleValue) + "centerThinningTransition" -> + setCenterThinningTransition(layer, styleValue) + "direction" -> + setDirection(layer, styleValue) + "directionTransition" -> + setDirectionTransition(layer, styleValue) + "flakeSize" -> + setFlakeSize(layer, styleValue) + "flakeSizeTransition" -> + setFlakeSizeTransition(layer, styleValue) + } + } catch (e: MapboxStyleException) { + Logger.e(LOG_TAG, "Failed to update: $styleKey ${e.message}") + } + } + } + fun setRainLayerStyle(layer: Rain, style: RNMBXStyle ) { + val styleKeys = style.allStyleKeys + + if (styleKeys.isEmpty()) { + return + } + + for (styleKey in styleKeys) { + try { + val styleValue = style.getStyleValueForKey(styleKey) + + when (styleKey) { + "density" -> + setDensity(layer, styleValue) + "densityTransition" -> + setDensityTransition(layer, styleValue) + "intensity" -> + setIntensity(layer, styleValue) + "intensityTransition" -> + setIntensityTransition(layer, styleValue) + "color" -> + setColor(layer, styleValue) + "colorTransition" -> + setColorTransition(layer, styleValue) + "opacity" -> + setOpacity(layer, styleValue) + "opacityTransition" -> + setOpacityTransition(layer, styleValue) + "vignette" -> + setVignette(layer, styleValue) + "vignetteTransition" -> + setVignetteTransition(layer, styleValue) + "vignetteColor" -> + setVignetteColor(layer, styleValue) + "vignetteColorTransition" -> + setVignetteColorTransition(layer, styleValue) + "centerThinning" -> + setCenterThinning(layer, styleValue) + "centerThinningTransition" -> + setCenterThinningTransition(layer, styleValue) + "direction" -> + setDirection(layer, styleValue) + "directionTransition" -> + setDirectionTransition(layer, styleValue) + "dropletSize" -> + setDropletSize(layer, styleValue) + "dropletSizeTransition" -> + setDropletSizeTransition(layer, styleValue) + "distortionStrength" -> + setDistortionStrength(layer, styleValue) + "distortionStrengthTransition" -> + setDistortionStrengthTransition(layer, styleValue) + } + } catch (e: MapboxStyleException) { + Logger.e(LOG_TAG, "Failed to update: $styleKey ${e.message}") + } + } + } fun setTerrainLayerStyle(layer: Terrain, style: RNMBXStyle ) { val styleKeys = style.allStyleKeys @@ -5671,6 +5785,500 @@ object RNMBXStyleFactory { } } + fun setDensity(layer: Snow, styleValue: RNMBXStyleValue ) { + if (styleValue.isExpression()) { + val expression = styleValue.getExpression() + if (expression != null) { + layer.density(expression) + } else { + Logger.e("RNMBXSnow", "Expression for density is null") + } + } else { + val value = styleValue.getDouble(VALUE_KEY) + if (value != null) { + layer.density(value) + } else { + Logger.e("RNMBXSnow", "value for density is null") + } + } + } + + + fun setDensityTransition(layer: Snow, styleValue: RNMBXStyleValue) { + val transition = styleValue.transition + if (transition != null) { + layer.densityTransition(transition); + } + } + + fun setIntensity(layer: Snow, styleValue: RNMBXStyleValue ) { + if (styleValue.isExpression()) { + val expression = styleValue.getExpression() + if (expression != null) { + layer.intensity(expression) + } else { + Logger.e("RNMBXSnow", "Expression for intensity is null") + } + } else { + val value = styleValue.getDouble(VALUE_KEY) + if (value != null) { + layer.intensity(value) + } else { + Logger.e("RNMBXSnow", "value for intensity is null") + } + } + } + + + fun setIntensityTransition(layer: Snow, styleValue: RNMBXStyleValue) { + val transition = styleValue.transition + if (transition != null) { + layer.intensityTransition(transition); + } + } + + fun setColor(layer: Snow, styleValue: RNMBXStyleValue ) { + if (styleValue.isExpression()) { + val expression = styleValue.getExpression() + if (expression != null) { + layer.color(expression) + } else { + Logger.e("RNMBXSnow", "Expression for color is null") + } + } else { + val value = styleValue.getInt(VALUE_KEY) + if (value != null) { + layer.color(value) + } else { + Logger.e("RNMBXSnow", "value for color is null") + } + } + } + + + fun setColorTransition(layer: Snow, styleValue: RNMBXStyleValue) { + val transition = styleValue.transition + if (transition != null) { + layer.colorTransition(transition); + } + } + + fun setOpacity(layer: Snow, styleValue: RNMBXStyleValue ) { + if (styleValue.isExpression()) { + val expression = styleValue.getExpression() + if (expression != null) { + layer.opacity(expression) + } else { + Logger.e("RNMBXSnow", "Expression for opacity is null") + } + } else { + val value = styleValue.getDouble(VALUE_KEY) + if (value != null) { + layer.opacity(value) + } else { + Logger.e("RNMBXSnow", "value for opacity is null") + } + } + } + + + fun setOpacityTransition(layer: Snow, styleValue: RNMBXStyleValue) { + val transition = styleValue.transition + if (transition != null) { + layer.opacityTransition(transition); + } + } + + fun setVignette(layer: Snow, styleValue: RNMBXStyleValue ) { + if (styleValue.isExpression()) { + val expression = styleValue.getExpression() + if (expression != null) { + layer.vignette(expression) + } else { + Logger.e("RNMBXSnow", "Expression for vignette is null") + } + } else { + val value = styleValue.getDouble(VALUE_KEY) + if (value != null) { + layer.vignette(value) + } else { + Logger.e("RNMBXSnow", "value for vignette is null") + } + } + } + + + fun setVignetteTransition(layer: Snow, styleValue: RNMBXStyleValue) { + val transition = styleValue.transition + if (transition != null) { + layer.vignetteTransition(transition); + } + } + + fun setVignetteColor(layer: Snow, styleValue: RNMBXStyleValue ) { + if (styleValue.isExpression()) { + val expression = styleValue.getExpression() + if (expression != null) { + layer.vignetteColor(expression) + } else { + Logger.e("RNMBXSnow", "Expression for vignetteColor is null") + } + } else { + val value = styleValue.getInt(VALUE_KEY) + if (value != null) { + layer.vignetteColor(value) + } else { + Logger.e("RNMBXSnow", "value for vignetteColor is null") + } + } + } + + + fun setVignetteColorTransition(layer: Snow, styleValue: RNMBXStyleValue) { + val transition = styleValue.transition + if (transition != null) { + layer.vignetteColorTransition(transition); + } + } + + fun setCenterThinning(layer: Snow, styleValue: RNMBXStyleValue ) { + if (styleValue.isExpression()) { + val expression = styleValue.getExpression() + if (expression != null) { + layer.centerThinning(expression) + } else { + Logger.e("RNMBXSnow", "Expression for centerThinning is null") + } + } else { + val value = styleValue.getDouble(VALUE_KEY) + if (value != null) { + layer.centerThinning(value) + } else { + Logger.e("RNMBXSnow", "value for centerThinning is null") + } + } + } + + + fun setCenterThinningTransition(layer: Snow, styleValue: RNMBXStyleValue) { + val transition = styleValue.transition + if (transition != null) { + layer.centerThinningTransition(transition); + } + } + + fun setDirection(layer: Snow, styleValue: RNMBXStyleValue ) { + if (styleValue.isExpression()) { + val expression = styleValue.getExpression() + if (expression != null) { + layer.direction(expression) + } else { + Logger.e("RNMBXSnow", "Expression for direction is null") + } + } else { + val value = styleValue.getFloatArray(VALUE_KEY) + if (value != null) { + layer.direction(value) + } else { + Logger.e("RNMBXSnow", "value for direction is null") + } + } + } + + + fun setDirectionTransition(layer: Snow, styleValue: RNMBXStyleValue) { + val transition = styleValue.transition + if (transition != null) { + layer.directionTransition(transition); + } + } + + fun setFlakeSize(layer: Snow, styleValue: RNMBXStyleValue ) { + if (styleValue.isExpression()) { + val expression = styleValue.getExpression() + if (expression != null) { + layer.flakeSize(expression) + } else { + Logger.e("RNMBXSnow", "Expression for flakeSize is null") + } + } else { + val value = styleValue.getDouble(VALUE_KEY) + if (value != null) { + layer.flakeSize(value) + } else { + Logger.e("RNMBXSnow", "value for flakeSize is null") + } + } + } + + + fun setFlakeSizeTransition(layer: Snow, styleValue: RNMBXStyleValue) { + val transition = styleValue.transition + if (transition != null) { + layer.flakeSizeTransition(transition); + } + } + + fun setDensity(layer: Rain, styleValue: RNMBXStyleValue ) { + if (styleValue.isExpression()) { + val expression = styleValue.getExpression() + if (expression != null) { + layer.density(expression) + } else { + Logger.e("RNMBXRain", "Expression for density is null") + } + } else { + val value = styleValue.getDouble(VALUE_KEY) + if (value != null) { + layer.density(value) + } else { + Logger.e("RNMBXRain", "value for density is null") + } + } + } + + + fun setDensityTransition(layer: Rain, styleValue: RNMBXStyleValue) { + val transition = styleValue.transition + if (transition != null) { + layer.densityTransition(transition); + } + } + + fun setIntensity(layer: Rain, styleValue: RNMBXStyleValue ) { + if (styleValue.isExpression()) { + val expression = styleValue.getExpression() + if (expression != null) { + layer.intensity(expression) + } else { + Logger.e("RNMBXRain", "Expression for intensity is null") + } + } else { + val value = styleValue.getDouble(VALUE_KEY) + if (value != null) { + layer.intensity(value) + } else { + Logger.e("RNMBXRain", "value for intensity is null") + } + } + } + + + fun setIntensityTransition(layer: Rain, styleValue: RNMBXStyleValue) { + val transition = styleValue.transition + if (transition != null) { + layer.intensityTransition(transition); + } + } + + fun setColor(layer: Rain, styleValue: RNMBXStyleValue ) { + if (styleValue.isExpression()) { + val expression = styleValue.getExpression() + if (expression != null) { + layer.color(expression) + } else { + Logger.e("RNMBXRain", "Expression for color is null") + } + } else { + val value = styleValue.getInt(VALUE_KEY) + if (value != null) { + layer.color(value) + } else { + Logger.e("RNMBXRain", "value for color is null") + } + } + } + + + fun setColorTransition(layer: Rain, styleValue: RNMBXStyleValue) { + val transition = styleValue.transition + if (transition != null) { + layer.colorTransition(transition); + } + } + + fun setOpacity(layer: Rain, styleValue: RNMBXStyleValue ) { + if (styleValue.isExpression()) { + val expression = styleValue.getExpression() + if (expression != null) { + layer.opacity(expression) + } else { + Logger.e("RNMBXRain", "Expression for opacity is null") + } + } else { + val value = styleValue.getDouble(VALUE_KEY) + if (value != null) { + layer.opacity(value) + } else { + Logger.e("RNMBXRain", "value for opacity is null") + } + } + } + + + fun setOpacityTransition(layer: Rain, styleValue: RNMBXStyleValue) { + val transition = styleValue.transition + if (transition != null) { + layer.opacityTransition(transition); + } + } + + fun setVignette(layer: Rain, styleValue: RNMBXStyleValue ) { + if (styleValue.isExpression()) { + val expression = styleValue.getExpression() + if (expression != null) { + layer.vignette(expression) + } else { + Logger.e("RNMBXRain", "Expression for vignette is null") + } + } else { + val value = styleValue.getDouble(VALUE_KEY) + if (value != null) { + layer.vignette(value) + } else { + Logger.e("RNMBXRain", "value for vignette is null") + } + } + } + + + fun setVignetteTransition(layer: Rain, styleValue: RNMBXStyleValue) { + val transition = styleValue.transition + if (transition != null) { + layer.vignetteTransition(transition); + } + } + + fun setVignetteColor(layer: Rain, styleValue: RNMBXStyleValue ) { + if (styleValue.isExpression()) { + val expression = styleValue.getExpression() + if (expression != null) { + layer.vignetteColor(expression) + } else { + Logger.e("RNMBXRain", "Expression for vignetteColor is null") + } + } else { + val value = styleValue.getInt(VALUE_KEY) + if (value != null) { + layer.vignetteColor(value) + } else { + Logger.e("RNMBXRain", "value for vignetteColor is null") + } + } + } + + + fun setVignetteColorTransition(layer: Rain, styleValue: RNMBXStyleValue) { + val transition = styleValue.transition + if (transition != null) { + layer.vignetteColorTransition(transition); + } + } + + fun setCenterThinning(layer: Rain, styleValue: RNMBXStyleValue ) { + if (styleValue.isExpression()) { + val expression = styleValue.getExpression() + if (expression != null) { + layer.centerThinning(expression) + } else { + Logger.e("RNMBXRain", "Expression for centerThinning is null") + } + } else { + val value = styleValue.getDouble(VALUE_KEY) + if (value != null) { + layer.centerThinning(value) + } else { + Logger.e("RNMBXRain", "value for centerThinning is null") + } + } + } + + + fun setCenterThinningTransition(layer: Rain, styleValue: RNMBXStyleValue) { + val transition = styleValue.transition + if (transition != null) { + layer.centerThinningTransition(transition); + } + } + + fun setDirection(layer: Rain, styleValue: RNMBXStyleValue ) { + if (styleValue.isExpression()) { + val expression = styleValue.getExpression() + if (expression != null) { + layer.direction(expression) + } else { + Logger.e("RNMBXRain", "Expression for direction is null") + } + } else { + val value = styleValue.getFloatArray(VALUE_KEY) + if (value != null) { + layer.direction(value) + } else { + Logger.e("RNMBXRain", "value for direction is null") + } + } + } + + + fun setDirectionTransition(layer: Rain, styleValue: RNMBXStyleValue) { + val transition = styleValue.transition + if (transition != null) { + layer.directionTransition(transition); + } + } + + fun setDropletSize(layer: Rain, styleValue: RNMBXStyleValue ) { + if (styleValue.isExpression()) { + val expression = styleValue.getExpression() + if (expression != null) { + layer.dropletSize(expression) + } else { + Logger.e("RNMBXRain", "Expression for dropletSize is null") + } + } else { + val value = styleValue.getFloatArray(VALUE_KEY) + if (value != null) { + layer.dropletSize(value) + } else { + Logger.e("RNMBXRain", "value for dropletSize is null") + } + } + } + + + fun setDropletSizeTransition(layer: Rain, styleValue: RNMBXStyleValue) { + val transition = styleValue.transition + if (transition != null) { + layer.dropletSizeTransition(transition); + } + } + + fun setDistortionStrength(layer: Rain, styleValue: RNMBXStyleValue ) { + if (styleValue.isExpression()) { + val expression = styleValue.getExpression() + if (expression != null) { + layer.distortionStrength(expression) + } else { + Logger.e("RNMBXRain", "Expression for distortionStrength is null") + } + } else { + val value = styleValue.getDouble(VALUE_KEY) + if (value != null) { + layer.distortionStrength(value) + } else { + Logger.e("RNMBXRain", "value for distortionStrength is null") + } + } + } + + + fun setDistortionStrengthTransition(layer: Rain, styleValue: RNMBXStyleValue) { + val transition = styleValue.transition + if (transition != null) { + layer.distortionStrengthTransition(transition); + } + } + fun setExaggeration(layer: Terrain, styleValue: RNMBXStyleValue ) { if (styleValue.isExpression()) { val expression = styleValue.getExpression() diff --git a/android/src/main/java/com/rnmapbox/rnmbx/components/styles/rain/RNMBXRain.kt b/android/src/main/java/com/rnmapbox/rnmbx/components/styles/rain/RNMBXRain.kt new file mode 100644 index 000000000..d3161becd --- /dev/null +++ b/android/src/main/java/com/rnmapbox/rnmbx/components/styles/rain/RNMBXRain.kt @@ -0,0 +1,66 @@ +package com.rnmapbox.rnmbx.components.styles.rain + +import android.content.Context +import com.facebook.react.bridge.ReadableMap +import com.mapbox.maps.MapboxMap +import com.mapbox.maps.extension.style.rain.generated.Rain +import com.mapbox.maps.extension.style.rain.generated.removeRain +import com.rnmapbox.rnmbx.components.RemovalReason +import com.rnmapbox.rnmbx.components.mapview.RNMBXMapView +import com.rnmapbox.rnmbx.components.styles.RNMBXStyle +import com.rnmapbox.rnmbx.components.styles.RNMBXStyleFactory +import com.rnmapbox.rnmbx.components.styles.sources.AbstractSourceConsumer +import com.rnmapbox.rnmbx.utils.Logger + +class RNMBXRain(context: Context?) : AbstractSourceConsumer(context) { + override var iD: String? = null + protected var mRain: Rain? = null + + // beginregion RNMBXLayer + @JvmField + protected var mMap: MapboxMap? = null + + @JvmField + protected var mReactStyle: ReadableMap? = null + + fun setReactStyle(reactStyle: ReadableMap?) { + mReactStyle = reactStyle + if (mRain != null) { + addStyles() + } + } + // endregion RNMBXLayer + + override fun addToMap(mapView: RNMBXMapView) { + super.addToMap(mapView) + mMap = mapView.getMapboxMap() + val rain = makeRain() + mRain = rain + addStyles() + mapView.savedStyle?.let { rain.bindTo(it) } + } + + override fun removeFromMap(mapView: RNMBXMapView, reason: RemovalReason): Boolean { + mapView.savedStyle?.let { it.removeRain() } + mRain = null + mMap = null + return super.removeFromMap(mapView, reason) + } + + fun makeRain(): Rain { + return Rain() + } + + fun addStyles() { + mRain?.also { + RNMBXStyleFactory.setRainLayerStyle( + it, RNMBXStyle( + context, mReactStyle, + mMap!! + ) + ) + } ?: run { + Logger.e("RNMBXRain", "mRain is null") + } + } +} diff --git a/android/src/main/java/com/rnmapbox/rnmbx/components/styles/rain/RNMBXRainManager.kt b/android/src/main/java/com/rnmapbox/rnmbx/components/styles/rain/RNMBXRainManager.kt new file mode 100644 index 000000000..5d1d40967 --- /dev/null +++ b/android/src/main/java/com/rnmapbox/rnmbx/components/styles/rain/RNMBXRainManager.kt @@ -0,0 +1,40 @@ +package com.rnmapbox.rnmbx.components.styles.rain + +import com.facebook.react.bridge.Dynamic +import com.facebook.react.uimanager.ThemedReactContext +import com.rnmapbox.rnmbx.utils.extensions.asMapOrNull +import com.facebook.react.uimanager.ViewGroupManager +import com.facebook.react.uimanager.ViewManagerDelegate +import com.facebook.react.uimanager.annotations.ReactProp +import com.facebook.react.viewmanagers.RNMBXRainManagerDelegate +import com.facebook.react.viewmanagers.RNMBXRainManagerInterface + +class RNMBXRainManager : ViewGroupManager(), RNMBXRainManagerInterface { + + private val mDelegate: ViewManagerDelegate + + init { + mDelegate = RNMBXRainManagerDelegate(this) + } + + override fun getDelegate(): ViewManagerDelegate { + return mDelegate + } + + override fun getName(): String { + return REACT_CLASS + } + + override fun createViewInstance(reactContext: ThemedReactContext): RNMBXRain { + return RNMBXRain(reactContext) + } + + @ReactProp(name = "reactStyle") + override fun setReactStyle(rain: RNMBXRain, reactStyle: Dynamic) { + rain.setReactStyle(reactStyle.asMapOrNull()) + } + + companion object { + const val REACT_CLASS = "RNMBXRain" + } +} diff --git a/android/src/main/java/com/rnmapbox/rnmbx/components/styles/snow/RNMBXSnow.kt b/android/src/main/java/com/rnmapbox/rnmbx/components/styles/snow/RNMBXSnow.kt new file mode 100644 index 000000000..44a27efeb --- /dev/null +++ b/android/src/main/java/com/rnmapbox/rnmbx/components/styles/snow/RNMBXSnow.kt @@ -0,0 +1,66 @@ +package com.rnmapbox.rnmbx.components.styles.snow + +import android.content.Context +import com.facebook.react.bridge.ReadableMap +import com.mapbox.maps.MapboxMap +import com.mapbox.maps.extension.style.snow.generated.Snow +import com.mapbox.maps.extension.style.snow.generated.removeSnow +import com.rnmapbox.rnmbx.components.RemovalReason +import com.rnmapbox.rnmbx.components.mapview.RNMBXMapView +import com.rnmapbox.rnmbx.components.styles.RNMBXStyle +import com.rnmapbox.rnmbx.components.styles.RNMBXStyleFactory +import com.rnmapbox.rnmbx.components.styles.sources.AbstractSourceConsumer +import com.rnmapbox.rnmbx.utils.Logger + +class RNMBXSnow(context: Context?) : AbstractSourceConsumer(context) { + override var iD: String? = null + protected var mSnow: Snow? = null + + // beginregion RNMBXLayer + @JvmField + protected var mMap: MapboxMap? = null + + @JvmField + protected var mReactStyle: ReadableMap? = null + + fun setReactStyle(reactStyle: ReadableMap?) { + mReactStyle = reactStyle + if (mSnow != null) { + addStyles() + } + } + // endregion RNMBXLayer + + override fun addToMap(mapView: RNMBXMapView) { + super.addToMap(mapView) + mMap = mapView.getMapboxMap() + val snow = makeSnow() + mSnow = snow + addStyles() + mapView.savedStyle?.let { snow.bindTo(it) } + } + + override fun removeFromMap(mapView: RNMBXMapView, reason: RemovalReason): Boolean { + mapView.savedStyle?.let { it.removeSnow() } + mSnow = null + mMap = null + return super.removeFromMap(mapView, reason) + } + + fun makeSnow(): Snow { + return Snow() + } + + fun addStyles() { + mSnow?.also { + RNMBXStyleFactory.setSnowLayerStyle( + it, RNMBXStyle( + context, mReactStyle, + mMap!! + ) + ) + } ?: run { + Logger.e("RNMBXSnow", "mSnow is null") + } + } +} diff --git a/android/src/main/java/com/rnmapbox/rnmbx/components/styles/snow/RNMBXSnowManager.kt b/android/src/main/java/com/rnmapbox/rnmbx/components/styles/snow/RNMBXSnowManager.kt new file mode 100644 index 000000000..31edfb506 --- /dev/null +++ b/android/src/main/java/com/rnmapbox/rnmbx/components/styles/snow/RNMBXSnowManager.kt @@ -0,0 +1,40 @@ +package com.rnmapbox.rnmbx.components.styles.snow + +import com.facebook.react.bridge.Dynamic +import com.facebook.react.uimanager.ThemedReactContext +import com.rnmapbox.rnmbx.utils.extensions.asMapOrNull +import com.facebook.react.uimanager.ViewGroupManager +import com.facebook.react.uimanager.ViewManagerDelegate +import com.facebook.react.uimanager.annotations.ReactProp +import com.facebook.react.viewmanagers.RNMBXSnowManagerDelegate +import com.facebook.react.viewmanagers.RNMBXSnowManagerInterface + +class RNMBXSnowManager : ViewGroupManager(), RNMBXSnowManagerInterface { + + private val mDelegate: ViewManagerDelegate + + init { + mDelegate = RNMBXSnowManagerDelegate(this) + } + + override fun getDelegate(): ViewManagerDelegate { + return mDelegate + } + + override fun getName(): String { + return REACT_CLASS + } + + override fun createViewInstance(reactContext: ThemedReactContext): RNMBXSnow { + return RNMBXSnow(reactContext) + } + + @ReactProp(name = "reactStyle") + override fun setReactStyle(snow: RNMBXSnow, reactStyle: Dynamic) { + snow.setReactStyle(reactStyle.asMapOrNull()) + } + + companion object { + const val REACT_CLASS = "RNMBXSnow" + } +} diff --git a/docs/Rain.md b/docs/Rain.md new file mode 100644 index 000000000..4febae2b7 --- /dev/null +++ b/docs/Rain.md @@ -0,0 +1,30 @@ + + + + +```tsx +import { Rain } from '@rnmapbox/maps'; + +Rain + +``` + + +## props + + +### style + +```tsx +RainLayerStyleProps +``` +FIX ME NO DESCRIPTION + + + + + + + + + diff --git a/docs/Snow.md b/docs/Snow.md new file mode 100644 index 000000000..c58b1d3a8 --- /dev/null +++ b/docs/Snow.md @@ -0,0 +1,30 @@ + + + + +```tsx +import { Snow } from '@rnmapbox/maps'; + +Snow + +``` + + +## props + + +### style + +```tsx +SnowLayerStyleProps +``` +FIX ME NO DESCRIPTION + + + + + + + + + diff --git a/docs/docs.json b/docs/docs.json index 3ef281b5a..35a69064d 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -6457,6 +6457,23 @@ "relPath": "src/components/PointAnnotation.tsx", "name": "PointAnnotation" }, + "Rain": { + "description": "", + "displayName": "Rain", + "methods": [], + "props": [ + { + "name": "style", + "required": false, + "type": "RainLayerStyleProps", + "default": "none", + "description": "FIX ME NO DESCRIPTION" + } + ], + "fileNameWithExt": "Rain.tsx", + "relPath": "src/components/Rain.tsx", + "name": "Rain" + }, "RasterArraySource": { "description": "RasterArraySource is a map content source that supplies raster array image tiles to be shown on the map.\nThis is typically used for particle animations like wind or precipitation patterns.\nThe location of and metadata about the tiles are defined either by an option dictionary\nor by an external file that conforms to the TileJSON specification.\n\n@experimental This component requires Mapbox Maps SDK v11.4.0 or later", "displayName": "RasterArraySource", @@ -7982,6 +7999,23 @@ } ] }, + "Snow": { + "description": "", + "displayName": "Snow", + "methods": [], + "props": [ + { + "name": "style", + "required": false, + "type": "SnowLayerStyleProps", + "default": "none", + "description": "FIX ME NO DESCRIPTION" + } + ], + "fileNameWithExt": "Snow.tsx", + "relPath": "src/components/Snow.tsx", + "name": "Snow" + }, "Style": { "description": "Style is a component that automatically adds sources / layers to the map using Mapbox GL Style Spec.\nOnly [`sources`](https://docs.mapbox.com/mapbox-gl-js/style-spec/sources) & [`layers`](https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/) are supported.\nOther fields such as `sprites`, `glyphs` etc. will be ignored. Not all layer / source attributes from the style spec are supported, in general the supported attributes will be mentioned under https://github.com/rnmapbox/maps/tree/main/docs.", "displayName": "Style", diff --git a/example/src/examples/V10/TerrainSkyAtmosphere.tsx b/example/src/examples/V10/TerrainSkyAtmosphere.tsx index e74048bba..441c6d4c4 100644 --- a/example/src/examples/V10/TerrainSkyAtmosphere.tsx +++ b/example/src/examples/V10/TerrainSkyAtmosphere.tsx @@ -1,5 +1,5 @@ -import { useRef } from 'react'; -import { Button } from 'react-native'; +import { useRef, useState } from 'react'; +import { Button, StyleSheet, View } from 'react-native'; import { MapView, SkyLayer, @@ -7,40 +7,48 @@ import { Terrain, RasterDemSource, Atmosphere, + Snow, + Rain, Camera, } from '@rnmapbox/maps'; Logger.setLogLevel('verbose'); +type WeatherEffect = 'none' | 'snow' | 'rain'; + const TerrainSkyAtmosphere = () => { const cameraRef = useRef(null); + const [weather, setWeather] = useState('none'); return ( - <> -