Skip to content

Commit

Permalink
Implement outline properties on Android (facebook#46284)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: facebook#46284

This diff adds:

`outline-width`: https://developer.mozilla.org/en-US/docs/Web/CSS/outline-width
`outline-color`: https://developer.mozilla.org/en-US/docs/Web/CSS/outline-color
`outline-style`: https://developer.mozilla.org/en-US/docs/Web/CSS/outline-style
`outline-offset`: https://developer.mozilla.org/en-US/docs/Web/CSS/outline-offset

Using `BackgroundStyleApplicator`

Changelog: [Android] [Added] - Outline properties `outline-width`, `outline-color`, `outline-style` & `outline-offset`

Differential Revision: D61293868
  • Loading branch information
jorge-cab authored and facebook-github-bot committed Sep 12, 2024
1 parent d0121a6 commit b9049f1
Show file tree
Hide file tree
Showing 16 changed files with 470 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = {
borderTopStartRadius: true,
cursor: true,
opacity: true,
outlineColor: colorAttributes,
outlineOffset: true,
outlineStyle: true,
outlineWidth: true,
pointerEvents: true,

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,11 @@ const validAttributesForNonEventProps = {
borderLeftWidth: true,
borderRightWidth: true,

outlineColor: {process: require('../StyleSheet/processColor').default},
outlineOffset: true,
outlineStyle: true,
outlineWidth: true,

start: true,
end: true,
left: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,10 @@ export interface ViewStyle extends FlexStyle, ShadowStyleIOS, TransformsStyle {
borderTopLeftRadius?: AnimatableNumericValue | string | undefined;
borderTopRightRadius?: AnimatableNumericValue | string | undefined;
borderTopStartRadius?: AnimatableNumericValue | string | undefined;
outlineColor?: ColorValue | undefined;
outlineOffset?: AnimatableNumericValue | undefined;
outlineStyle?: 'solid' | 'dotted' | 'dashed' | undefined;
outlineWidth?: AnimatableNumericValue | undefined;
opacity?: AnimatableNumericValue | undefined;
/**
* Sets the elevation of a view, using Android's underlying
Expand Down
4 changes: 4 additions & 0 deletions packages/react-native/Libraries/StyleSheet/StyleSheetTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,10 @@ export type ____ViewStyle_InternalCore = $ReadOnly<{
borderStartWidth?: AnimatableNumericValue,
borderTopWidth?: AnimatableNumericValue,
opacity?: AnimatableNumericValue,
outlineColor?: ____ColorValue_Internal,
outlineOffset?: AnimatableNumericValue,
outlineStyle?: 'solid' | 'dotted' | 'dashed',
outlineWidth?: AnimatableNumericValue,
elevation?: number,
pointerEvents?: 'auto' | 'none' | 'box-none' | 'box-only',
cursor?: CursorValue,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8363,6 +8363,10 @@ export type ____ViewStyle_InternalCore = $ReadOnly<{
borderStartWidth?: AnimatableNumericValue,
borderTopWidth?: AnimatableNumericValue,
opacity?: AnimatableNumericValue,
outlineColor?: ____ColorValue_Internal,
outlineOffset?: AnimatableNumericValue,
outlineStyle?: \\"solid\\" | \\"dotted\\" | \\"dashed\\",
outlineWidth?: AnimatableNumericValue,
elevation?: number,
pointerEvents?: \\"auto\\" | \\"none\\" | \\"box-none\\" | \\"box-only\\",
cursor?: CursorValue,
Expand Down
14 changes: 13 additions & 1 deletion packages/react-native/ReactAndroid/api/ReactAndroid.api
Original file line number Diff line number Diff line change
Expand Up @@ -4136,6 +4136,10 @@ public final class com/facebook/react/uimanager/BackgroundStyleApplicator {
public static final fun setBoxShadow (Landroid/view/View;Lcom/facebook/react/bridge/ReadableArray;)V
public static final fun setBoxShadow (Landroid/view/View;Ljava/util/List;)V
public static final fun setFeedbackUnderlay (Landroid/view/View;Landroid/graphics/drawable/Drawable;)V
public static final fun setOutlineColor (Landroid/view/View;Ljava/lang/Integer;)V
public static final fun setOutlineOffset (Landroid/view/View;F)V
public static final fun setOutlineStyle (Landroid/view/View;Lcom/facebook/react/uimanager/style/BorderStyle;)V
public static final fun setOutlineWidth (Landroid/view/View;F)V
}

public abstract class com/facebook/react/uimanager/BaseViewManager : com/facebook/react/uimanager/ViewManager, android/view/View$OnLayoutChangeListener, com/facebook/react/uimanager/BaseViewManagerInterface {
Expand Down Expand Up @@ -4171,6 +4175,10 @@ public abstract class com/facebook/react/uimanager/BaseViewManager : com/faceboo
public fun setMoveShouldSetResponderCapture (Landroid/view/View;Z)V
public fun setNativeId (Landroid/view/View;Ljava/lang/String;)V
public fun setOpacity (Landroid/view/View;F)V
public fun setOutlineColor (Landroid/view/View;Ljava/lang/Integer;)V
public fun setOutlineOffset (Landroid/view/View;F)V
public fun setOutlineStyle (Landroid/view/View;Ljava/lang/String;)V
public fun setOutlineWidth (Landroid/view/View;F)V
public fun setPointerEnter (Landroid/view/View;Z)V
public fun setPointerEnterCapture (Landroid/view/View;Z)V
public fun setPointerLeave (Landroid/view/View;Z)V
Expand Down Expand Up @@ -5643,6 +5651,10 @@ public final class com/facebook/react/uimanager/ViewProps {
public static final field ON Ljava/lang/String;
public static final field ON_LAYOUT Ljava/lang/String;
public static final field OPACITY Ljava/lang/String;
public static final field OUTLINE_COLOR Ljava/lang/String;
public static final field OUTLINE_OFFSET Ljava/lang/String;
public static final field OUTLINE_STYLE Ljava/lang/String;
public static final field OUTLINE_WIDTH Ljava/lang/String;
public static final field OVERFLOW Ljava/lang/String;
public static final field PADDING Ljava/lang/String;
public static final field PADDING_BOTTOM Ljava/lang/String;
Expand Down Expand Up @@ -6194,11 +6206,11 @@ public final class com/facebook/react/uimanager/style/CornerRadii {
public final fun component2 ()F
public final fun copy (FF)Lcom/facebook/react/uimanager/style/CornerRadii;
public static synthetic fun copy$default (Lcom/facebook/react/uimanager/style/CornerRadii;FFILjava/lang/Object;)Lcom/facebook/react/uimanager/style/CornerRadii;
public final fun dpToPx ()Lcom/facebook/react/uimanager/style/CornerRadii;
public fun equals (Ljava/lang/Object;)Z
public final fun getHorizontal ()F
public final fun getVertical ()F
public fun hashCode ()I
public final fun toPixelFromDIP ()Lcom/facebook/react/uimanager/style/CornerRadii;
public fun toString ()Ljava/lang/String;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.facebook.react.uimanager.drawable.CompositeBackgroundDrawable
import com.facebook.react.uimanager.drawable.InsetBoxShadowDrawable
import com.facebook.react.uimanager.drawable.MIN_INSET_BOX_SHADOW_SDK_VERSION
import com.facebook.react.uimanager.drawable.MIN_OUTSET_BOX_SHADOW_SDK_VERSION
import com.facebook.react.uimanager.drawable.OutlineDrawable
import com.facebook.react.uimanager.drawable.OutsetBoxShadowDrawable
import com.facebook.react.uimanager.style.BackgroundImageLayer
import com.facebook.react.uimanager.style.BorderInsets
Expand All @@ -33,6 +34,7 @@ import com.facebook.react.uimanager.style.BorderRadiusStyle
import com.facebook.react.uimanager.style.BorderStyle
import com.facebook.react.uimanager.style.BoxShadow
import com.facebook.react.uimanager.style.LogicalEdge
import com.facebook.react.uimanager.style.OutlineStyle

/**
* BackgroundStyleApplicator is responsible for applying backgrounds, borders, and related effects,
Expand Down Expand Up @@ -80,6 +82,52 @@ public object BackgroundStyleApplicator {
}
}

@JvmStatic
public fun setOutlineColor(view: View, @ColorInt outlineColor: Int?): Unit {
val outline = ensureOutlineDrawable(view)
if (outlineColor != null) {
outline.outlineColor = outlineColor
}
}

@JvmStatic
public fun setOutlineOffset(view: View, outlineOffset: Float): Unit {
val outline = ensureOutlineDrawable(view)
outline.outlineOffset = outlineOffset.dpToPx()
}

@JvmStatic
public fun setOutlineStyle(view: View, outlineStyle: OutlineStyle?): Unit {
val outline = ensureOutlineDrawable(view)
if (outlineStyle != null) {
outline.outlineStyle = outlineStyle
}
}

@JvmStatic
public fun setOutlineWidth(view: View, width: Float): Unit {
val outline = ensureOutlineDrawable(view)
outline.outlineWidth = width.dpToPx()
}

private fun ensureOutlineDrawable(view: View): OutlineDrawable {
var outline = ensureCompositeBackgroundDrawable(view).outline
if (outline == null) {
outline =
OutlineDrawable(
context = view.context,
borderRadius = ensureCSSBackground(view).borderRadius,
outlineColor = Color.BLACK,
outlineOffset = 0f,
outlineStyle = OutlineStyle.SOLID,
outlineWidth = 0f,
)
view.background = ensureCompositeBackgroundDrawable(view).withNewOutline(outline)
}

return outline
}

@JvmStatic
public fun getBorderWidth(view: View, edge: LogicalEdge): Float? {
val width = getCSSBackground(view)?.getBorderWidth(edge.toSpacingType())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags;
import com.facebook.react.uimanager.ReactAccessibilityDelegate.AccessibilityRole;
import com.facebook.react.uimanager.ReactAccessibilityDelegate.Role;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.common.UIManagerType;
import com.facebook.react.uimanager.common.ViewUtil;
import com.facebook.react.uimanager.events.PointerEventHelper;
import com.facebook.react.uimanager.style.OutlineStyle;
import com.facebook.react.uimanager.util.ReactFindViewUtil;
import java.util.ArrayList;
import java.util.HashMap;
Expand Down Expand Up @@ -780,6 +782,37 @@ public void setBorderTopRightRadius(T view, float borderRadius) {
logUnsupportedPropertyWarning(ViewProps.BORDER_TOP_RIGHT_RADIUS);
}

@ReactProp(name = ViewProps.OUTLINE_COLOR, customType = "Color")
public void setOutlineColor(T view, @Nullable Integer color) {
if (ReactNativeFeatureFlags.enableBackgroundStyleApplicator()) {
BackgroundStyleApplicator.setOutlineColor(view, color);
}
}

@ReactProp(name = ViewProps.OUTLINE_OFFSET)
public void setOutlineOffset(T view, float offset) {
if (ReactNativeFeatureFlags.enableBackgroundStyleApplicator()) {
BackgroundStyleApplicator.setOutlineOffset(view, offset);
}
}

@ReactProp(name = ViewProps.OUTLINE_STYLE)
public void setOutlineStyle(T view, @Nullable String outlineStyle) {
if (ReactNativeFeatureFlags.enableBackgroundStyleApplicator()) {
@Nullable
OutlineStyle parsedOutlineStyle =
outlineStyle == null ? null : OutlineStyle.fromString(outlineStyle);
BackgroundStyleApplicator.setOutlineStyle(view, parsedOutlineStyle);
}
}

@ReactProp(name = ViewProps.OUTLINE_WIDTH)
public void setOutlineWidth(T view, float width) {
if (ReactNativeFeatureFlags.enableBackgroundStyleApplicator()) {
BackgroundStyleApplicator.setOutlineWidth(view, width);
}
}

private void logUnsupportedPropertyWarning(String propName) {
FLog.w(ReactConstants.TAG, "%s doesn't support property '%s'", getName(), propName);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ public object ViewProps {
public const val BOX_SHADOW: String = "boxShadow"
public const val FILTER: String = "filter"
public const val MIX_BLEND_MODE: String = "experimental_mixBlendMode"
public const val OUTLINE_COLOR: String = "outlineColor"
public const val OUTLINE_OFFSET: String = "outlineOffset"
public const val OUTLINE_STYLE: String = "outlineStyle"
public const val OUTLINE_WIDTH: String = "outlineWidth"
public const val TRANSFORM: String = "transform"
public const val TRANSFORM_ORIGIN: String = "transformOrigin"
public const val ELEVATION: String = "elevation"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -673,10 +673,10 @@ private void updatePath() {
mContext,
PixelUtil.toDIPFromPixel(mOuterClipTempRectForBorderRadius.width()),
PixelUtil.toDIPFromPixel(mOuterClipTempRectForBorderRadius.height()));
CornerRadii topLeftRadius = mComputedBorderRadius.getTopLeft().toPixelFromDIP();
CornerRadii topRightRadius = mComputedBorderRadius.getTopRight().toPixelFromDIP();
CornerRadii bottomLeftRadius = mComputedBorderRadius.getBottomLeft().toPixelFromDIP();
CornerRadii bottomRightRadius = mComputedBorderRadius.getBottomRight().toPixelFromDIP();
CornerRadii topLeftRadius = mComputedBorderRadius.getTopLeft().dpToPx();
CornerRadii topRightRadius = mComputedBorderRadius.getTopRight().dpToPx();
CornerRadii bottomLeftRadius = mComputedBorderRadius.getBottomLeft().dpToPx();
CornerRadii bottomRightRadius = mComputedBorderRadius.getBottomRight().dpToPx();

final float innerTopLeftRadiusX =
getInnerBorderRadius(topLeftRadius.getHorizontal(), borderWidth.left);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ internal class CompositeBackgroundDrawable(

/** Inset box-shadows */
public val innerShadows: List<Drawable> = emptyList(),

/** Outline */
public val outline: OutlineDrawable? = null,
) :
LayerDrawable(
listOfNotNull(
Expand All @@ -50,7 +53,8 @@ internal class CompositeBackgroundDrawable(
*outerShadows.asReversed().toTypedArray(),
cssBackground,
feedbackUnderlay,
*innerShadows.asReversed().toTypedArray())
*innerShadows.asReversed().toTypedArray(),
outline)
.toTypedArray()) {

// Holder value for currently set insets
Expand All @@ -67,19 +71,24 @@ internal class CompositeBackgroundDrawable(
cssBackground: CSSBackgroundDrawable?
): CompositeBackgroundDrawable {
return CompositeBackgroundDrawable(
originalBackground, outerShadows, cssBackground, feedbackUnderlay, innerShadows)
originalBackground, outerShadows, cssBackground, feedbackUnderlay, innerShadows, outline)
}

public fun withNewShadows(
outerShadows: List<Drawable>,
innerShadows: List<Drawable>
): CompositeBackgroundDrawable {
return CompositeBackgroundDrawable(
originalBackground, outerShadows, cssBackground, feedbackUnderlay, innerShadows)
originalBackground, outerShadows, cssBackground, feedbackUnderlay, innerShadows, outline)
}

public fun withNewOutline(outline: OutlineDrawable): CompositeBackgroundDrawable {
return CompositeBackgroundDrawable(
originalBackground, outerShadows, cssBackground, feedbackUnderlay, innerShadows, outline)
}

public fun withNewFeedbackUnderlay(newUnderlay: Drawable?): CompositeBackgroundDrawable {
return CompositeBackgroundDrawable(
originalBackground, outerShadows, cssBackground, newUnderlay, innerShadows)
originalBackground, outerShadows, cssBackground, newUnderlay, innerShadows, outline)
}
}
Loading

0 comments on commit b9049f1

Please sign in to comment.