From 7ae0eac13a5d3ab1916d43606fdbc2e2d7fd8a09 Mon Sep 17 00:00:00 2001 From: Lorenzo Blasa Date: Fri, 11 Nov 2022 04:49:02 -0800 Subject: [PATCH] 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 --- .../descriptors/DebugComponentDescriptor.kt | 14 +- .../props/ComponentPropExtractor.kt | 133 +++++++++++ .../descriptors/props/LayoutPropExtractor.kt | 210 ++++++++++++++++++ .../plugins/uidebugger/model/Inspectable.kt | 4 + .../flipper/plugins/uidebugger/model/Types.kt | 2 +- 5 files changed, 361 insertions(+), 2 deletions(-) create mode 100644 android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/props/ComponentPropExtractor.kt create mode 100644 android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/props/LayoutPropExtractor.kt diff --git a/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/DebugComponentDescriptor.kt b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/DebugComponentDescriptor.kt index a37bc858c..3614697e3 100644 --- a/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/DebugComponentDescriptor.kt +++ b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/DebugComponentDescriptor.kt @@ -10,6 +10,8 @@ package com.facebook.flipper.plugins.uidebugger.litho.descriptors import android.graphics.Bitmap import com.facebook.flipper.plugins.uidebugger.descriptors.* 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.InspectableObject import com.facebook.flipper.plugins.uidebugger.model.MetadataId @@ -57,10 +59,20 @@ class DebugComponentDescriptor(val register: DescriptorRegister) : NodeDescripto private val LayoutId = MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "Litho Layout") 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 { + val attributeSections = mutableMapOf() + 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 } diff --git a/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/props/ComponentPropExtractor.kt b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/props/ComponentPropExtractor.kt new file mode 100644 index 000000000..48160e2ec --- /dev/null +++ b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/props/ComponentPropExtractor.kt @@ -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 { + val props = mutableMapOf() + + 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 { + override fun isShape(shape: EditorShape): Inspectable { + + val fields = mutableMapOf() + 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) + }) + } +} diff --git a/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/props/LayoutPropExtractor.kt b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/props/LayoutPropExtractor.kt new file mode 100644 index 000000000..936b4d8cb --- /dev/null +++ b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/props/LayoutPropExtractor.kt @@ -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 { + val props = mutableMapOf() + + 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(), layout.layoutDirection.name)) + + props[FlexDirectionId] = + InspectableValue.Enum( + Enumeration(enumToSet(), layout.flexDirection.name)) + props[JustifyContentId] = + InspectableValue.Enum(Enumeration(enumToSet(), layout.justifyContent.name)) + props[AlignItemsId] = + InspectableValue.Enum(Enumeration(enumToSet(), layout.alignItems.name)) + props[AlignSelfId] = + InspectableValue.Enum(Enumeration(enumToSet(), layout.alignSelf.name)) + props[AlignContentId] = + InspectableValue.Enum(Enumeration(enumToSet(), layout.alignContent.name)) + props[PositionTypeId] = + InspectableValue.Enum(Enumeration(enumToSet(), 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() + 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() + 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() + 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() + 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 > enumerator(): Iterator = enumValues().iterator() + private inline fun > enumToSet(): Set { + val set = mutableSetOf() + val values = enumerator() + values.forEach { set.add(it.name) } + return set + } +} diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Inspectable.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Inspectable.kt index 42c7a82cc..a4c511689 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Inspectable.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Inspectable.kt @@ -89,6 +89,10 @@ sealed class InspectableValue : Inspectable() { val value: com.facebook.flipper.plugins.uidebugger.model.Enumeration, ) : InspectableValue() + @SerialName("unknown") + @kotlinx.serialization.Serializable + data class Unknown(val value: String?) : InspectableValue() {} + companion object { /** * Will attempt to convert Any ref to a suitable primitive inspectable value. Only use if you diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Types.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Types.kt index 9933d7c09..89eaa47f1 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Types.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Types.kt @@ -64,4 +64,4 @@ data class Size( ) {} @kotlinx.serialization.Serializable -data class Enumeration(val values: Set, val value: String) +data class Enumeration(val values: Set, val value: String?)