Skip to content

Commit

Permalink
Implement outline properties on Android (#46284)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #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`

Reviewed By: NickGerleman

Differential Revision: D61293868

fbshipit-source-id: d8787bbf1560cf46b92ad039afde9c1e7ab669da
  • Loading branch information
jorge-cab authored and facebook-github-bot committed Sep 14, 2024
1 parent 40aaeb7 commit 17faac4
Show file tree
Hide file tree
Showing 18 changed files with 572 additions and 5 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 @@ -411,6 +411,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
31 changes: 31 additions & 0 deletions packages/react-native/ReactAndroid/api/ReactAndroid.api
Original file line number Diff line number Diff line change
Expand Up @@ -4128,6 +4128,10 @@ public final class com/facebook/react/uimanager/BackgroundStyleApplicator {
public static final fun getBorderRadius (Landroid/view/View;Lcom/facebook/react/uimanager/style/BorderRadiusProp;)Lcom/facebook/react/uimanager/LengthPercentage;
public static final fun getBorderStyle (Landroid/view/View;)Lcom/facebook/react/uimanager/style/BorderStyle;
public static final fun getBorderWidth (Landroid/view/View;Lcom/facebook/react/uimanager/style/LogicalEdge;)Ljava/lang/Float;
public static final fun getOutlineColor (Landroid/view/View;)Ljava/lang/Integer;
public final fun getOutlineOffset (Landroid/view/View;)Ljava/lang/Float;
public final fun getOutlineStyle (Landroid/view/View;)Lcom/facebook/react/uimanager/style/OutlineStyle;
public final fun getOutlineWidth (Landroid/view/View;)Ljava/lang/Float;
public static final fun reset (Landroid/view/View;)V
public static final fun setBackgroundColor (Landroid/view/View;Ljava/lang/Integer;)V
public static final fun setBackgroundImage (Landroid/view/View;Ljava/util/List;)V
Expand All @@ -4138,6 +4142,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/OutlineStyle;)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 @@ -4173,6 +4181,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 @@ -5645,6 +5657,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 @@ -6235,6 +6251,21 @@ public final class com/facebook/react/uimanager/style/LogicalEdge$Companion {
public final fun fromSpacingType (I)Lcom/facebook/react/uimanager/style/LogicalEdge;
}

public final class com/facebook/react/uimanager/style/OutlineStyle : java/lang/Enum {
public static final field Companion Lcom/facebook/react/uimanager/style/OutlineStyle$Companion;
public static final field DASHED Lcom/facebook/react/uimanager/style/OutlineStyle;
public static final field DOTTED Lcom/facebook/react/uimanager/style/OutlineStyle;
public static final field SOLID Lcom/facebook/react/uimanager/style/OutlineStyle;
public static final fun fromString (Ljava/lang/String;)Lcom/facebook/react/uimanager/style/OutlineStyle;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public static fun valueOf (Ljava/lang/String;)Lcom/facebook/react/uimanager/style/OutlineStyle;
public static fun values ()[Lcom/facebook/react/uimanager/style/OutlineStyle;
}

public final class com/facebook/react/uimanager/style/OutlineStyle$Companion {
public final fun fromString (Ljava/lang/String;)Lcom/facebook/react/uimanager/style/OutlineStyle;
}

public final class com/facebook/react/uimanager/style/Overflow : java/lang/Enum {
public static final field Companion Lcom/facebook/react/uimanager/style/Overflow$Companion;
public static final field HIDDEN Lcom/facebook/react/uimanager/style/Overflow;
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 @@ -123,6 +125,13 @@ public object BackgroundStyleApplicator {
}
}
}

val outline = compositeBackgroundDrawable.outline
if (outline != null) {
outline.borderRadius = outline.borderRadius ?: BorderRadiusStyle()
outline.borderRadius?.set(corner, radius)
outline.invalidateSelf()
}
}

@JvmStatic
Expand All @@ -137,6 +146,42 @@ public object BackgroundStyleApplicator {
@JvmStatic
public fun getBorderStyle(view: View): BorderStyle? = getCSSBackground(view)?.borderStyle

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

@JvmStatic public fun getOutlineColor(view: View): Int? = getOutlineDrawable(view)?.outlineColor

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

public fun getOutlineOffset(view: View): Float? = getOutlineDrawable(view)?.outlineOffset

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

public fun getOutlineStyle(view: View): OutlineStyle? = getOutlineDrawable(view)?.outlineStyle

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

public fun getOutlineWidth(view: View): Float? = getOutlineDrawable(view)?.outlineOffset

@JvmStatic
public fun setBoxShadow(view: View, shadows: List<BoxShadow>): Unit {
if (ViewUtil.getUIManagerType(view) != UIManagerType.FABRIC) {
Expand Down Expand Up @@ -260,4 +305,26 @@ public object BackgroundStyleApplicator {

private fun getCSSBackground(view: View): CSSBackgroundDrawable? =
getCompositeBackgroundDrawable(view)?.cssBackground

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

return outline
}

private fun getOutlineDrawable(view: View): OutlineDrawable? =
getCompositeBackgroundDrawable(view)?.outline
}
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 @@ -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 17faac4

Please sign in to comment.