diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index e89803ec44a..0a6b0643803 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -4003,6 +4003,7 @@ public abstract class com/facebook/react/uimanager/BaseViewManager : com/faceboo public fun setElevation (Landroid/view/View;F)V public fun setFilter (Landroid/view/View;Lcom/facebook/react/bridge/ReadableArray;)V public fun setImportantForAccessibility (Landroid/view/View;Ljava/lang/String;)V + public fun setMixBlendMode (Landroid/view/View;Ljava/lang/String;)V public fun setMoveShouldSetResponder (Landroid/view/View;Z)V public fun setMoveShouldSetResponderCapture (Landroid/view/View;Z)V public fun setNativeId (Landroid/view/View;Ljava/lang/String;)V @@ -4073,6 +4074,7 @@ public abstract interface class com/facebook/react/uimanager/BaseViewManagerInte public abstract fun setElevation (Landroid/view/View;F)V public abstract fun setFilter (Landroid/view/View;Lcom/facebook/react/bridge/ReadableArray;)V public abstract fun setImportantForAccessibility (Landroid/view/View;Ljava/lang/String;)V + public abstract fun setMixBlendMode (Landroid/view/View;Ljava/lang/String;)V public abstract fun setNativeId (Landroid/view/View;Ljava/lang/String;)V public abstract fun setOpacity (Landroid/view/View;F)V public abstract fun setRenderToHardwareTexture (Landroid/view/View;Z)V @@ -5449,6 +5451,7 @@ public final class com/facebook/react/uimanager/ViewProps { public static final field MINIMUM_FONT_SCALE Ljava/lang/String; public static final field MIN_HEIGHT Ljava/lang/String; public static final field MIN_WIDTH Ljava/lang/String; + public static final field MIX_BLEND_MODE Ljava/lang/String; public static final field NATIVE_ID Ljava/lang/String; public static final field NEEDS_OFFSCREEN_ALPHA_COMPOSITING Ljava/lang/String; public static final field NONE Ljava/lang/String; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java index 4012c71fe19..05c23a605db 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java @@ -7,6 +7,7 @@ package com.facebook.react.uimanager; +import android.graphics.BlendMode; import android.graphics.Color; import android.graphics.Paint; import android.os.Build; @@ -107,7 +108,7 @@ public abstract class BaseViewManager= Build.VERSION_CODES.P) { @@ -192,6 +193,12 @@ public abstract class BaseViewManager= Build.VERSION_CODES.S) { view.setRenderEffect(null); } - Boolean useHWLayer = (Boolean) view.getTag(R.id.use_hardware_layer); - int layerType = - useHWLayer != null && useHWLayer ? View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE; - view.setLayerType(layerType, null); - if (filter == null) { - return; + @Nullable Paint p = null; + + if (filter != null) { + if (FilterHelper.isOnlyColorMatrixFilters(filter)) { + p = new Paint(); + p.setColorFilter(FilterHelper.parseColorMatrixFilters(filter)); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + view.setRenderEffect(FilterHelper.parseFilters(filter)); + } } - if (FilterHelper.isOnlyColorMatrixFilters(filter)) { - Paint p = new Paint(); - p.setColorFilter(FilterHelper.parseColorMatrixFilters(filter)); + if (mixBlendMode != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + p = p == null ? new Paint() : p; + p.setBlendMode(mixBlendMode); + } + + if (p == null) { + int layerType = + useHWLayer != null && useHWLayer ? View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE; + view.setLayerType(layerType, null); + } else { view.setLayerType(View.LAYER_TYPE_HARDWARE, p); - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - view.setRenderEffect(FilterHelper.parseFilters(filter)); } } @@ -625,13 +644,14 @@ public abstract class BaseViewManager= Build.VERSION_CODES.S + ? (BlendMode) view.getTag(R.id.mix_blend_mode) + : null; + Boolean useHWLayer = (Boolean) view.getTag(R.id.use_hardware_layer); + + applyLayerEffects(view, filter, mixBlendMode, useHWLayer); } @Override diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerInterface.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerInterface.java index 631989da43e..2731a8af478 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerInterface.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerInterface.java @@ -50,6 +50,8 @@ public interface BaseViewManagerInterface { void setFilter(T view, ReadableArray filter); + void setMixBlendMode(T view, String setMixBlendMode); + void setShadowColor(T view, int shadowColor); void setImportantForAccessibility(T view, @Nullable String importantForAccessibility); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BlendModeHelper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BlendModeHelper.kt new file mode 100644 index 00000000000..22fef9a2c9f --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BlendModeHelper.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.uimanager + +import android.annotation.TargetApi +import android.graphics.BlendMode + +@TargetApi(29) +internal object BlendModeHelper { + + /** @see https://www.w3.org/TR/compositing-1/#mix-blend-mode */ + @JvmStatic + public fun parseMixBlendMode(mixBlendMode: String?): BlendMode? { + mixBlendMode ?: return null + + return when (mixBlendMode) { + "normal" -> null + "multiply" -> BlendMode.MULTIPLY + "screen" -> BlendMode.SCREEN + "overlay" -> BlendMode.OVERLAY + "darken" -> BlendMode.DARKEN + "lighten" -> BlendMode.LIGHTEN + "color-dodge" -> BlendMode.COLOR_DODGE + "color-burn" -> BlendMode.COLOR_BURN + "hard-light" -> BlendMode.HARD_LIGHT + "soft-light" -> BlendMode.SOFT_LIGHT + "difference" -> BlendMode.DIFFERENCE + "exclusion" -> BlendMode.EXCLUSION + "hue" -> BlendMode.HUE + "saturation" -> BlendMode.SATURATION + "color" -> BlendMode.COLOR + "luminosity" -> BlendMode.LUMINOSITY + else -> throw IllegalArgumentException("Invalid mix-blend-mode name: $mixBlendMode") + } + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.kt index 5b6c2bef0d6..95544335f54 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.kt @@ -136,6 +136,7 @@ public object ViewProps { public const val BORDER_END_COLOR: String = "borderEndColor" public const val ON_LAYOUT: String = "onLayout" public const val FILTER: String = "experimental_filter" + public const val MIX_BLEND_MODE: String = "experimental_mixBlendMode" public const val TRANSFORM: String = "transform" public const val TRANSFORM_ORIGIN: String = "transformOrigin" public const val ELEVATION: String = "elevation" diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values/ids.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values/ids.xml index 93958b203d3..f8dd27bb5bd 100644 --- a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values/ids.xml +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values/ids.xml @@ -62,4 +62,7 @@ + + + diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp index 6ef4c07522e..93969024935 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp @@ -166,6 +166,15 @@ BaseViewProps::BaseViewProps( "experimental_filter", sourceProps.filter, {})), + mixBlendMode( + CoreFeatures::enablePropIteratorSetter + ? sourceProps.mixBlendMode + : convertRawProp( + context, + rawProps, + "experimental_mixBlendMode", + sourceProps.mixBlendMode, + {})), transform( CoreFeatures::enablePropIteratorSetter ? sourceProps.transform : convertRawProp( diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h index 218b9721104..9602c719cdd 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h @@ -62,6 +62,9 @@ class BaseViewProps : public YogaStylableProps, public AccessibilityProps { // Filter std::vector filter{}; + // MixBlendMode + std::string mixBlendMode; + // Transform Transform transform{}; TransformOrigin transformOrigin{ diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/ViewShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/ViewShadowNode.cpp index 9df83d2270b..c3ca8496af6 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/ViewShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/ViewShadowNode.cpp @@ -61,7 +61,7 @@ void ViewShadowNode::initialize() noexcept { viewProps.accessibilityViewIsModal || viewProps.importantForAccessibility != ImportantForAccessibility::Auto || viewProps.removeClippedSubviews || viewProps.cursor != Cursor::Auto || - !viewProps.filter.empty() || + !viewProps.filter.empty() || !viewProps.mixBlendMode.empty() || HostPlatformViewTraitsInitializer::formsStackingContext(viewProps); bool formsView = formsStackingContext ||