Litho Props

Summary:
This diff adds support for layout and component props from Litho.

Notes:
- Each component could register a descriptor for itself.

Reviewed By: LukeDefeo

Differential Revision: D40680095

fbshipit-source-id: 57c78a199db58e05dd6dac4ed32ff6a869a73b0a
This commit is contained in:
Lorenzo Blasa
2022-11-11 04:49:02 -08:00
committed by Facebook GitHub Bot
parent 612bd69605
commit 7ae0eac13a
5 changed files with 361 additions and 2 deletions

View File

@@ -10,6 +10,8 @@ package com.facebook.flipper.plugins.uidebugger.litho.descriptors
import android.graphics.Bitmap import android.graphics.Bitmap
import com.facebook.flipper.plugins.uidebugger.descriptors.* import com.facebook.flipper.plugins.uidebugger.descriptors.*
import com.facebook.flipper.plugins.uidebugger.litho.LithoTag import com.facebook.flipper.plugins.uidebugger.litho.LithoTag
import com.facebook.flipper.plugins.uidebugger.litho.descriptors.props.ComponentPropExtractor
import com.facebook.flipper.plugins.uidebugger.litho.descriptors.props.LayoutPropExtractor
import com.facebook.flipper.plugins.uidebugger.model.Bounds import com.facebook.flipper.plugins.uidebugger.model.Bounds
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
import com.facebook.flipper.plugins.uidebugger.model.MetadataId import com.facebook.flipper.plugins.uidebugger.model.MetadataId
@@ -57,10 +59,20 @@ class DebugComponentDescriptor(val register: DescriptorRegister) : NodeDescripto
private val LayoutId = private val LayoutId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "Litho Layout") MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "Litho Layout")
private val UserPropsId = private val UserPropsId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "User Props") MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "Litho Props")
override fun getData(node: DebugComponent): Map<MetadataId, InspectableObject> { override fun getData(node: DebugComponent): Map<MetadataId, InspectableObject> {
val attributeSections = mutableMapOf<MetadataId, InspectableObject>() val attributeSections = mutableMapOf<MetadataId, InspectableObject>()
val layoutProps = LayoutPropExtractor.getProps(node)
attributeSections[LayoutId] = InspectableObject(layoutProps.toMap())
if (!node.canResolve()) {
val props = ComponentPropExtractor.getProps(node.component)
attributeSections[UserPropsId] = InspectableObject(props.toMap())
}
return attributeSections return attributeSections
} }

View File

@@ -0,0 +1,133 @@
/*
* 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.flipper.plugins.uidebugger.litho.descriptors.props
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister
import com.facebook.flipper.plugins.uidebugger.model.*
import com.facebook.litho.Component
import com.facebook.litho.SpecGeneratedComponent
import com.facebook.litho.annotations.Prop
import com.facebook.litho.annotations.ResType
import com.facebook.litho.editor.EditorRegistry
import com.facebook.litho.editor.model.EditorArray
import com.facebook.litho.editor.model.EditorBool
import com.facebook.litho.editor.model.EditorColor
import com.facebook.litho.editor.model.EditorNumber
import com.facebook.litho.editor.model.EditorPick
import com.facebook.litho.editor.model.EditorShape
import com.facebook.litho.editor.model.EditorString
import com.facebook.litho.editor.model.EditorValue
import com.facebook.litho.editor.model.EditorValue.EditorVisitor
import com.facebook.yoga.*
object ComponentPropExtractor {
private const val NAMESPACE = "ComponentPropExtractor"
fun getProps(component: Component): Map<MetadataId, Inspectable> {
val props = mutableMapOf<MetadataId, Inspectable>()
val isSpecComponent = component is SpecGeneratedComponent
for (declaredField in component.javaClass.declaredFields) {
declaredField.isAccessible = true
val name = declaredField.name
val metadata = MetadataRegister.get(component.simpleName, name)
val identifier =
metadata?.id
?: MetadataRegister.registerDynamic(
MetadataRegister.TYPE_ATTRIBUTE, component.simpleName, name)
val declaredFieldAnnotation = declaredField.getAnnotation(Prop::class.java)
// Only expose `@Prop` annotated fields for Spec components
if (isSpecComponent && declaredFieldAnnotation == null) {
continue
}
val prop =
try {
declaredField[component]
} catch (e: IllegalAccessException) {
continue
}
if (declaredFieldAnnotation != null) {
val resType = declaredFieldAnnotation.resType
if (resType == ResType.COLOR) {
if (prop != null) {
props[identifier] = InspectableValue.Color(Color.fromColor(prop as Int))
}
continue
} else if (resType == ResType.DRAWABLE) {
props[identifier] = fromDrawable(prop as Drawable?)
continue
}
}
val editorValue: EditorValue? =
EditorRegistry.read(declaredField.type, declaredField, component)
if (editorValue != null) {
props[identifier] = toInspectable(name, editorValue)
}
}
return props
}
private fun fromDrawable(d: Drawable?): Inspectable =
when (d) {
is ColorDrawable -> InspectableValue.Color(Color.fromColor(d.color))
else -> InspectableValue.Unknown(d.toString())
}
private fun toInspectable(name: String, editorValue: EditorValue): Inspectable {
return editorValue.`when`(
object : EditorVisitor<Inspectable> {
override fun isShape(shape: EditorShape): Inspectable {
val fields = mutableMapOf<MetadataId, Inspectable>()
shape.value.entries.forEach { entry ->
val metadata = MetadataRegister.get(name, entry.key)
val identifier =
metadata?.id
?: MetadataRegister.registerDynamic(
MetadataRegister.TYPE_LAYOUT, name, entry.key)
val value = toInspectable(entry.key, entry.value)
fields[identifier] = value
}
return InspectableObject(fields)
}
override fun isArray(array: EditorArray?): Inspectable {
val values = array?.value?.map { value -> toInspectable(name, value) }
return InspectableArray(0, values ?: listOf())
}
override fun isPick(pick: EditorPick?): Inspectable =
InspectableValue.Enum(Enumeration(pick?.values ?: setOf(), pick?.selected))
override fun isNumber(number: EditorNumber): Inspectable =
InspectableValue.Number(number.value)
override fun isColor(number: EditorColor): Inspectable =
InspectableValue.Color(number.value.toInt().let { Color.fromColor(it) })
override fun isString(string: EditorString?): Inspectable =
InspectableValue.Text(string?.value ?: "")
override fun isBool(bool: EditorBool): Inspectable = InspectableValue.Boolean(bool.value)
})
}
}

View File

@@ -0,0 +1,210 @@
/*
* 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.flipper.plugins.uidebugger.litho.descriptors.props
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister
import com.facebook.flipper.plugins.uidebugger.model.*
import com.facebook.litho.DebugComponent
import com.facebook.yoga.*
object LayoutPropExtractor {
private const val NAMESPACE = "LayoutPropExtractor"
private var BackgroundId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "background")
private var ForegroundId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "foreground")
private val DirectionId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "direction")
private val FlexDirectionId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "flexDirection")
private val JustifyContentId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "justifyContent")
private val AlignItemsId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "alignItems")
private val AlignSelfId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "alignSelf")
private val AlignContentId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "alignContent")
private val PositionTypeId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "positionType")
private val FlexGrowId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "flexGrow")
private val FlexShrinkId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "flexShrink")
private val FlexBasisId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "flexBasis")
private val WidthId = MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "width")
private val HeightId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "height")
private val MinWidthId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "minWidth")
private val MinHeightId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "minHeight")
private val MaxWidthId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "maxWidth")
private val MaxHeightId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "maxHeight")
private val AspectRatioId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "aspectRatio")
private val MarginId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "margin")
private val PaddingId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "padding")
private val BorderId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "border")
private val PositionId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "position")
private val LeftId = MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "left")
private val TopId = MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "top")
private val RightId = MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "right")
private val BottomId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "bottom")
private val StartId = MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "start")
private val EndId = MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "end")
private val HorizontalId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "horizontal")
private val VerticalId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "vertical")
private val AllId = MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "all")
private val HasViewOutputId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "hasViewOutput")
private val AlphaId = MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "alpha")
private val ScaleId = MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "scale")
private val RotationId =
MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "rotation")
fun getProps(component: DebugComponent): Map<MetadataId, Inspectable> {
val props = mutableMapOf<MetadataId, Inspectable>()
val layout = component.layoutNode ?: return props
layout.background?.let { drawable -> props[BackgroundId] = fromDrawable(drawable) }
layout.foreground?.let { drawable -> props[ForegroundId] = fromDrawable(drawable) }
props[DirectionId] =
InspectableValue.Enum(Enumeration(enumToSet<YogaDirection>(), layout.layoutDirection.name))
props[FlexDirectionId] =
InspectableValue.Enum(
Enumeration(enumToSet<YogaFlexDirection>(), layout.flexDirection.name))
props[JustifyContentId] =
InspectableValue.Enum(Enumeration(enumToSet<YogaJustify>(), layout.justifyContent.name))
props[AlignItemsId] =
InspectableValue.Enum(Enumeration(enumToSet<YogaAlign>(), layout.alignItems.name))
props[AlignSelfId] =
InspectableValue.Enum(Enumeration(enumToSet<YogaAlign>(), layout.alignSelf.name))
props[AlignContentId] =
InspectableValue.Enum(Enumeration(enumToSet<YogaAlign>(), layout.alignContent.name))
props[PositionTypeId] =
InspectableValue.Enum(Enumeration(enumToSet<YogaPositionType>(), layout.positionType.name))
props[FlexGrowId] = InspectableValue.Text(layout.flexGrow.toString())
props[FlexShrinkId] = InspectableValue.Text(layout.flexShrink.toString())
props[FlexBasisId] = InspectableValue.Text(layout.flexBasis.toString())
props[WidthId] = InspectableValue.Text(layout.width.toString())
props[MinWidthId] = InspectableValue.Text(layout.minWidth.toString())
props[MaxWidthId] = InspectableValue.Text(layout.maxWidth.toString())
props[HeightId] = InspectableValue.Text(layout.height.toString())
props[MinHeightId] = InspectableValue.Text(layout.minHeight.toString())
props[MaxHeightId] = InspectableValue.Text(layout.maxHeight.toString())
props[AspectRatioId] = InspectableValue.Text(layout.aspectRatio.toString())
val marginProps = mutableMapOf<MetadataId, Inspectable>()
marginProps[LeftId] = InspectableValue.Text(layout.getMargin(YogaEdge.LEFT).toString())
marginProps[TopId] = InspectableValue.Text(layout.getMargin(YogaEdge.TOP).toString())
marginProps[RightId] = InspectableValue.Text(layout.getMargin(YogaEdge.RIGHT).toString())
marginProps[BottomId] = InspectableValue.Text(layout.getMargin(YogaEdge.BOTTOM).toString())
marginProps[StartId] = InspectableValue.Text(layout.getMargin(YogaEdge.START).toString())
marginProps[EndId] = InspectableValue.Text(layout.getMargin(YogaEdge.END).toString())
marginProps[HorizontalId] =
InspectableValue.Text(layout.getMargin(YogaEdge.HORIZONTAL).toString())
marginProps[VerticalId] = InspectableValue.Text(layout.getMargin(YogaEdge.VERTICAL).toString())
marginProps[AllId] = InspectableValue.Text(layout.getMargin(YogaEdge.ALL).toString())
props[MarginId] = InspectableObject(marginProps)
val paddingProps = mutableMapOf<MetadataId, Inspectable>()
paddingProps[LeftId] = InspectableValue.Text(layout.getPadding(YogaEdge.LEFT).toString())
paddingProps[TopId] = InspectableValue.Text(layout.getPadding(YogaEdge.TOP).toString())
paddingProps[RightId] = InspectableValue.Text(layout.getPadding(YogaEdge.RIGHT).toString())
paddingProps[BottomId] = InspectableValue.Text(layout.getPadding(YogaEdge.BOTTOM).toString())
paddingProps[StartId] = InspectableValue.Text(layout.getPadding(YogaEdge.START).toString())
paddingProps[EndId] = InspectableValue.Text(layout.getPadding(YogaEdge.END).toString())
paddingProps[HorizontalId] =
InspectableValue.Text(layout.getPadding(YogaEdge.HORIZONTAL).toString())
paddingProps[VerticalId] =
InspectableValue.Text(layout.getPadding(YogaEdge.VERTICAL).toString())
paddingProps[AllId] = InspectableValue.Text(layout.getPadding(YogaEdge.ALL).toString())
props[PaddingId] = InspectableObject(paddingProps)
val borderProps = mutableMapOf<MetadataId, Inspectable>()
borderProps[LeftId] = InspectableValue.Text(layout.getBorderWidth(YogaEdge.LEFT).toString())
borderProps[TopId] = InspectableValue.Text(layout.getBorderWidth(YogaEdge.TOP).toString())
borderProps[RightId] = InspectableValue.Text(layout.getBorderWidth(YogaEdge.RIGHT).toString())
borderProps[BottomId] = InspectableValue.Text(layout.getBorderWidth(YogaEdge.BOTTOM).toString())
borderProps[StartId] = InspectableValue.Text(layout.getBorderWidth(YogaEdge.START).toString())
borderProps[EndId] = InspectableValue.Text(layout.getBorderWidth(YogaEdge.END).toString())
borderProps[HorizontalId] =
InspectableValue.Text(layout.getBorderWidth(YogaEdge.HORIZONTAL).toString())
borderProps[VerticalId] =
InspectableValue.Text(layout.getBorderWidth(YogaEdge.VERTICAL).toString())
borderProps[AllId] = InspectableValue.Text(layout.getBorderWidth(YogaEdge.ALL).toString())
props[BorderId] = InspectableObject(borderProps)
val positionProps = mutableMapOf<MetadataId, Inspectable>()
positionProps[LeftId] = InspectableValue.Text(layout.getPosition(YogaEdge.LEFT).toString())
positionProps[TopId] = InspectableValue.Text(layout.getPosition(YogaEdge.TOP).toString())
positionProps[RightId] = InspectableValue.Text(layout.getPosition(YogaEdge.RIGHT).toString())
positionProps[BottomId] = InspectableValue.Text(layout.getPosition(YogaEdge.BOTTOM).toString())
positionProps[StartId] = InspectableValue.Text(layout.getPosition(YogaEdge.START).toString())
positionProps[EndId] = InspectableValue.Text(layout.getPosition(YogaEdge.END).toString())
positionProps[HorizontalId] =
InspectableValue.Text(layout.getPosition(YogaEdge.HORIZONTAL).toString())
positionProps[VerticalId] =
InspectableValue.Text(layout.getPosition(YogaEdge.VERTICAL).toString())
positionProps[AllId] = InspectableValue.Text(layout.getPosition(YogaEdge.ALL).toString())
props[PositionId] = InspectableObject(positionProps)
props[HasViewOutputId] = InspectableValue.Boolean(layout.hasViewOutput())
if (layout.hasViewOutput()) {
props[AlphaId] = InspectableValue.Number(layout.alpha)
props[ScaleId] = InspectableValue.Number(layout.scale)
props[RotationId] = InspectableValue.Number(layout.rotation)
}
return props
}
private fun fromDrawable(d: Drawable?): Inspectable =
when (d) {
is ColorDrawable -> InspectableValue.Color(Color.fromColor(d.color))
else -> InspectableValue.Unknown(d.toString())
}
private inline fun <reified T : Enum<T>> enumerator(): Iterator<T> = enumValues<T>().iterator()
private inline fun <reified T : Enum<T>> enumToSet(): Set<String> {
val set = mutableSetOf<String>()
val values = enumerator<T>()
values.forEach { set.add(it.name) }
return set
}
}

View File

@@ -89,6 +89,10 @@ sealed class InspectableValue : Inspectable() {
val value: com.facebook.flipper.plugins.uidebugger.model.Enumeration, val value: com.facebook.flipper.plugins.uidebugger.model.Enumeration,
) : InspectableValue() ) : InspectableValue()
@SerialName("unknown")
@kotlinx.serialization.Serializable
data class Unknown(val value: String?) : InspectableValue() {}
companion object { companion object {
/** /**
* Will attempt to convert Any ref to a suitable primitive inspectable value. Only use if you * Will attempt to convert Any ref to a suitable primitive inspectable value. Only use if you

View File

@@ -64,4 +64,4 @@ data class Size(
) {} ) {}
@kotlinx.serialization.Serializable @kotlinx.serialization.Serializable
data class Enumeration(val values: Set<String>, val value: String) data class Enumeration(val values: Set<String>, val value: String?)