Export android plugin to OSS (#5109)

Summary:
Changelog: UI Debugger is now available for Litho in Open Source

Pull Request resolved: https://github.com/facebook/flipper/pull/5109

Remove the stub, replace with the real thing.

Reviewed By: lblasa

Differential Revision: D46859213

fbshipit-source-id: 74c59a53d1d22e046254f4bca202da17a0b0e5d8
This commit is contained in:
Pascal Hartig
2023-09-08 04:02:48 -07:00
committed by Facebook GitHub Bot
parent d4065aba12
commit 0900a2a41d
9 changed files with 1105 additions and 16 deletions

View File

@@ -0,0 +1,124 @@
/*
* 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
import com.facebook.flipper.plugins.uidebugger.core.ConnectionListener
import com.facebook.flipper.plugins.uidebugger.core.UIDContext
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister
import com.facebook.flipper.plugins.uidebugger.litho.descriptors.*
import com.facebook.flipper.plugins.uidebugger.model.FrameworkEvent
import com.facebook.flipper.plugins.uidebugger.model.FrameworkEventMetadata
import com.facebook.litho.ComponentTree
import com.facebook.litho.DebugComponent
import com.facebook.litho.LithoView
import com.facebook.litho.MatrixDrawable
import com.facebook.litho.debug.LithoDebugEvent
import com.facebook.litho.widget.TextDrawable
import com.facebook.rendercore.debug.DebugEvent
import com.facebook.rendercore.debug.DebugEventAttribute
import com.facebook.rendercore.debug.DebugEventBus
import com.facebook.rendercore.debug.DebugEventSubscriber
import com.facebook.rendercore.debug.DebugMarkerEvent
import com.facebook.rendercore.debug.DebugProcessEvent
import com.facebook.rendercore.debug.Duration
const val LithoTag = "Litho"
const val LithoMountableTag = "LithoMountable"
object UIDebuggerLithoSupport {
fun enable(context: UIDContext) {
addDescriptors(context.descriptorRegister)
val eventMeta =
listOf(
// Litho
FrameworkEventMetadata(
LithoDebugEvent.StateUpdateEnqueued,
"Set state was called, this will trigger resolve and then possibly layout and mount"),
FrameworkEventMetadata(
LithoDebugEvent.RenderRequest,
"A request to render the component tree again. It can be requested due to 1) set root 2) state update 3) size change or measurement"),
FrameworkEventMetadata(
LithoDebugEvent.ComponentTreeResolve,
"ComponentTree resolved the hierarchy into a LayoutState, non layout nodes are removed, see attributes for source of execution"),
FrameworkEventMetadata(
LithoDebugEvent.LayoutCommitted,
"A new layout state created (resolved and measured result) being committed; this layout state could get mounted next."),
// RenderCore
FrameworkEventMetadata(
DebugEvent.RenderTreeMounted, "The mount phase for the entire render tree"),
FrameworkEventMetadata(
DebugEvent.RenderUnitMounted,
"Component was added into the view hierarchy (this doesn't mean it is visible)"),
FrameworkEventMetadata(
DebugEvent.RenderUnitUpdated,
"The properties of a component's content were were rebinded"),
FrameworkEventMetadata(
DebugEvent.RenderUnitUnmounted, "Component was removed from the view hierarchy"),
FrameworkEventMetadata(DebugEvent.RenderUnitOnVisible, "Component became visible"),
FrameworkEventMetadata(DebugEvent.RenderUnitOnInvisible, "Component became invisible"),
)
val eventForwarder =
object : DebugEventSubscriber(*eventMeta.map { it.type }.toTypedArray()) {
override fun onEvent(event: DebugEvent) {
val timestamp =
when (event) {
is DebugMarkerEvent -> event.timestamp
is DebugProcessEvent -> event.timestamp
}
val treeId = event.renderStateId.toIntOrNull() ?: -1
val globalKey =
event.attributeOrNull<String>(DebugEventAttribute.GlobalKey)?.let {
DebugComponent.generateGlobalKey(treeId, it).hashCode()
}
val duration = event.attributeOrNull<Duration>(DebugEventAttribute.duration)
val attributes = mutableMapOf<String, String>()
val source = event.attributeOrNull<String>(DebugEventAttribute.source)
if (source != null) {
attributes["source"] = source
}
context.addFrameworkEvent(
FrameworkEvent(
treeId,
globalKey ?: treeId,
event.type,
timestamp,
duration?.value,
event.threadName,
attributes))
}
}
context.connectionListeners.add(
object : ConnectionListener {
override fun onConnect() {
DebugEventBus.subscribe(eventForwarder)
}
override fun onDisconnect() {
DebugEventBus.unsubscribe(eventForwarder)
}
})
context.frameworkEventMetadata.addAll(eventMeta)
}
private fun addDescriptors(register: DescriptorRegister) {
register.register(LithoView::class.java, LithoViewDescriptor)
register.register(DebugComponent::class.java, DebugComponentDescriptor(register))
register.register(TextDrawable::class.java, TextDrawableDescriptor)
register.register(MatrixDrawable::class.java, MatrixDrawableDescriptor)
register.register(ComponentTree::class.java, ComponentTreeDescriptor(register))
}
}

View File

@@ -1,16 +0,0 @@
/*
* 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
import com.facebook.flipper.plugins.uidebugger.core.UIDContext
// this is not used internally
object UIDebuggerLithoSupport {
fun enable(context: UIDContext) {}
}

View File

@@ -0,0 +1,63 @@
/*
* 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
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister
import com.facebook.flipper.plugins.uidebugger.descriptors.Id
import com.facebook.flipper.plugins.uidebugger.descriptors.NodeDescriptor
import com.facebook.flipper.plugins.uidebugger.descriptors.OffsetChild
import com.facebook.flipper.plugins.uidebugger.litho.LithoTag
import com.facebook.flipper.plugins.uidebugger.model.Bounds
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
import com.facebook.flipper.plugins.uidebugger.util.Immediate
import com.facebook.flipper.plugins.uidebugger.util.MaybeDeferred
import com.facebook.litho.ComponentTree
import com.facebook.litho.DebugComponent
class ComponentTreeDescriptor(val register: DescriptorRegister) : NodeDescriptor<ComponentTree> {
private val qualifiedName = ComponentTree::class.qualifiedName ?: ""
override fun getId(node: ComponentTree): Id = node.id
override fun getBounds(node: ComponentTree): Bounds {
val rootComponent = DebugComponent.getRootInstance(node)
return if (rootComponent != null) {
Bounds.fromRect(rootComponent.boundsInParentDebugComponent)
} else {
Bounds(0, 0, 0, 0)
}
}
override fun getName(node: ComponentTree): String = "ComponentTree"
override fun getQualifiedName(node: ComponentTree): String = qualifiedName
override fun getChildren(node: ComponentTree): List<Any> {
val result = mutableListOf<Any>()
val debugComponent = DebugComponent.getRootInstance(node)
if (debugComponent != null) {
result.add(
// we want the component tree to take the size and any offset so we reset this one
OffsetChild.zero(
debugComponent, register.descriptorForClassUnsafe(debugComponent.javaClass)))
}
return result
}
override fun getActiveChild(node: ComponentTree): Any? = null
override fun getAttributes(
node: ComponentTree
): MaybeDeferred<Map<MetadataId, InspectableObject>> {
return Immediate(mapOf())
}
override fun getTags(node: ComponentTree): Set<String> = setOf(LithoTag, "TreeRoot")
}

View File

@@ -0,0 +1,188 @@
/*
* 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
import android.graphics.Bitmap
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister
import com.facebook.flipper.plugins.uidebugger.descriptors.Id
import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister
import com.facebook.flipper.plugins.uidebugger.descriptors.NodeDescriptor
import com.facebook.flipper.plugins.uidebugger.descriptors.OffsetChild
import com.facebook.flipper.plugins.uidebugger.litho.LithoMountableTag
import com.facebook.flipper.plugins.uidebugger.litho.LithoTag
import com.facebook.flipper.plugins.uidebugger.litho.descriptors.props.ComponentDataExtractor
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.Inspectable
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
import com.facebook.flipper.plugins.uidebugger.model.InspectableValue
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
import com.facebook.flipper.plugins.uidebugger.util.Deferred
import com.facebook.flipper.plugins.uidebugger.util.MaybeDeferred
import com.facebook.litho.Component
import com.facebook.litho.DebugComponent
import com.facebook.rendercore.FastMath
import com.facebook.yoga.YogaEdge
class DebugComponentDescriptor(val register: DescriptorRegister) : NodeDescriptor<DebugComponent> {
private val NAMESPACE = "DebugComponent"
/*
* Debug component is generated on the fly so use the underlying component instance which is
* immutable
*/
override fun getId(node: DebugComponent): Id = node.globalKey.hashCode()
override fun getName(node: DebugComponent): String = node.component.simpleName
override fun getQualifiedName(node: com.facebook.litho.DebugComponent): String =
node.component::class.qualifiedName ?: ""
override fun getChildren(node: DebugComponent): List<Any> {
val result = mutableListOf<Any>()
val mountedContent = node.mountedContent
if (mountedContent == null) {
for (child in node.childComponents) {
result.add(child)
}
} else {
val layoutNode = node.layoutNode
val descriptor: NodeDescriptor<Any> =
register.descriptorForClassUnsafe(mountedContent.javaClass)
// mountables are always layout nodes
if (layoutNode != null) {
/**
* We need to override the mounted contents offset since the mounted contents android bounds
* are w.r.t its native parent but we want it w.r.t to the mountable.
*
* However padding on a mountable means that the content is inset within the mountables
* bounds so we need to adjust for this
*/
result.add(
OffsetChild(
child = mountedContent,
descriptor = descriptor,
x = layoutNode.getLayoutPadding(YogaEdge.LEFT).let { FastMath.round(it) },
y = layoutNode.getLayoutPadding(YogaEdge.TOP).let { FastMath.round(it) },
))
}
}
return result
}
override fun getActiveChild(node: DebugComponent): Any? = null
private val LayoutId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "Litho Layout")
private val UserPropsId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "Litho Props")
private val StateId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "Litho State")
private val MountingDataId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "Mount State")
private val isMountedAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "mounted")
private val isVisibleAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "visible")
override fun getAttributes(
node: DebugComponent
): MaybeDeferred<Map<MetadataId, InspectableObject>> {
return Deferred {
val attributeSections = mutableMapOf<MetadataId, InspectableObject>()
val mountingData = getMountingData(node)
attributeSections[MountingDataId] = InspectableObject(mountingData)
val layoutProps = LayoutPropExtractor.getProps(node)
attributeSections[LayoutId] = InspectableObject(layoutProps.toMap())
if (!node.canResolve()) {
val stateContainer = node.stateContainer
if (stateContainer != null) {
attributeSections[StateId] =
ComponentDataExtractor.getState(stateContainer, node.component.simpleName)
}
val props = ComponentDataExtractor.getProps(node.component)
attributeSections[UserPropsId] = InspectableObject(props.toMap())
}
attributeSections
}
}
override fun getBounds(node: DebugComponent): Bounds =
Bounds.fromRect(node.boundsInParentDebugComponent)
override fun getTags(node: DebugComponent): Set<String> {
val tags = mutableSetOf(LithoTag)
if (node.component.mountType != Component.MountType.NONE) {
tags.add(LithoMountableTag)
}
return tags
}
override fun getSnapshot(node: DebugComponent, bitmap: Bitmap?): Bitmap? = null
override fun getInlineAttributes(node: DebugComponent): Map<String, String> {
val attributes = mutableMapOf<String, String>()
val key = node.key
val testKey = node.testKey
if (key != null && key.trim { it <= ' ' }.length > 0) {
attributes["key"] = key
}
if (testKey != null && testKey.trim { it <= ' ' }.length > 0) {
attributes["testKey"] = testKey
}
return attributes
}
private fun getMountingData(node: DebugComponent): Map<Id, Inspectable> {
val lithoView = node.lithoView
val mountingData = mutableMapOf<MetadataId, Inspectable>()
if (lithoView == null) {
return mountingData
}
val mountState = lithoView.mountDelegateTarget ?: return mountingData
val componentTree = lithoView.componentTree ?: return mountingData
val component = node.component
if (component.mountType != Component.MountType.NONE) {
val renderUnit = DebugComponent.getRenderUnit(node, componentTree)
if (renderUnit != null) {
val renderUnitId = renderUnit.id
val isMounted = mountState.getContentById(renderUnitId) != null
mountingData[isMountedAttributeId] = InspectableValue.Boolean(isMounted)
}
}
val visibilityOutput = DebugComponent.getVisibilityOutput(node, componentTree)
if (visibilityOutput != null) {
val isVisible = DebugComponent.isVisible(node, lithoView)
mountingData[isVisibleAttributeId] = InspectableValue.Boolean(isVisible)
}
return mountingData
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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
import com.facebook.flipper.plugins.uidebugger.descriptors.ChainedDescriptor
import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
import com.facebook.flipper.plugins.uidebugger.model.InspectableValue
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
import com.facebook.litho.LithoView
object LithoViewDescriptor : ChainedDescriptor<LithoView>() {
private const val NAMESPACE = "LithoView"
private val SectionId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE)
override fun onGetName(node: LithoView): String = node.javaClass.simpleName
override fun onGetChildren(node: LithoView): List<Any> {
val componentTree = node.componentTree
if (componentTree != null) {
return listOf(componentTree)
}
return listOf()
}
private val IsIncrementalMountEnabledAttributeId =
MetadataRegister.register(
MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "isIncrementalMountEnabled")
override fun onGetAttributes(
node: LithoView,
attributeSections: MutableMap<MetadataId, InspectableObject>
) {
attributeSections[SectionId] =
InspectableObject(
mapOf(
IsIncrementalMountEnabledAttributeId to
InspectableValue.Boolean(node.isIncrementalMountEnabled)))
}
}

View File

@@ -0,0 +1,25 @@
/*
* 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
import com.facebook.flipper.plugins.uidebugger.descriptors.ChainedDescriptor
import com.facebook.litho.MatrixDrawable
object MatrixDrawableDescriptor : ChainedDescriptor<MatrixDrawable<*>>() {
override fun onGetChildren(node: MatrixDrawable<*>): List<Any>? {
val mountedDrawable = node.mountedDrawable
return if (mountedDrawable != null) {
listOf(mountedDrawable)
} else {
listOf()
}
}
override fun onGetName(node: MatrixDrawable<*>): String = node.javaClass.simpleName
}

View File

@@ -0,0 +1,38 @@
/*
* 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
import com.facebook.flipper.plugins.uidebugger.descriptors.ChainedDescriptor
import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister
import com.facebook.flipper.plugins.uidebugger.model.Inspectable
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
import com.facebook.flipper.plugins.uidebugger.model.InspectableValue
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
import com.facebook.litho.widget.TextDrawable
object TextDrawableDescriptor : ChainedDescriptor<TextDrawable>() {
private const val NAMESPACE = "TextDrawable"
private val SectionId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE)
override fun onGetName(node: TextDrawable): String = node.javaClass.simpleName
private val TextAttributeId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "text")
override fun onGetAttributes(
node: TextDrawable,
attributeSections: MutableMap<MetadataId, InspectableObject>
) {
val props =
mapOf<Int, Inspectable>(TextAttributeId to InspectableValue.Text(node.text.toString()))
attributeSections[SectionId] = InspectableObject(props)
}
}

View File

@@ -0,0 +1,187 @@
/*
* 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 android.util.Log
import com.facebook.flipper.plugins.uidebugger.LogTag
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.StateContainer
import com.facebook.litho.annotations.Prop
import com.facebook.litho.annotations.ResType
import com.facebook.litho.annotations.State
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
object ComponentDataExtractor {
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 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) {
val identifier = getMetadataId(component.simpleName, name)
props[identifier] = InspectableValue.Color(Color.fromColor(prop as Int))
}
continue
} else if (resType == ResType.DRAWABLE) {
val identifier = getMetadataId(component.simpleName, name)
props[identifier] = fromDrawable(prop as Drawable?)
continue
}
}
val editorValue =
try {
EditorRegistry.read(declaredField.type, declaredField, component)
} catch (e: Exception) {
Log.d(
LogTag,
"Unable to retrieve prop ${declaredField.name} on type ${component.simpleName}")
EditorString("error fetching prop")
}
if (editorValue != null) {
addProp(props, component.simpleName, name, editorValue)
}
}
return props
}
fun getState(stateContainer: StateContainer, componentName: String): InspectableObject {
val stateFields = mutableMapOf<MetadataId, Inspectable>()
for (field in stateContainer.javaClass.declaredFields) {
field.isAccessible = true
val stateAnnotation = field.getAnnotation(State::class.java)
val isKStateField = field.name == "states"
if (stateAnnotation != null || isKStateField) {
val id = getMetadataId(componentName, field.name)
val editorValue: EditorValue? = EditorRegistry.read(field.type, field, stateContainer)
if (editorValue != null) {
stateFields[id] = toInspectable(field.name, editorValue)
}
}
}
return InspectableObject(stateFields)
}
private fun getMetadataId(
namespace: String,
key: String,
mutable: Boolean = false,
possibleValues: Set<InspectableValue>? = emptySet()
): MetadataId {
val metadata = MetadataRegister.get(namespace, key)
val identifier =
metadata?.id
?: MetadataRegister.register(
MetadataRegister.TYPE_ATTRIBUTE, namespace, key, mutable, possibleValues)
return identifier
}
private fun addProp(
props: MutableMap<MetadataId, Inspectable>,
namespace: String,
name: String,
value: EditorValue
) {
var possibleValues: MutableSet<InspectableValue>? = null
if (value is EditorPick) {
possibleValues = mutableSetOf()
value.values.forEach { possibleValues.add(InspectableValue.Text(it)) }
}
val identifier = getMetadataId(namespace, name, false, possibleValues)
props[identifier] = toInspectable(name, value)
}
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 value = toInspectable(entry.key, entry.value)
val shapeEditorValue = entry.value
var possibleValues: MutableSet<InspectableValue>? = null
if (shapeEditorValue is EditorPick) {
possibleValues = mutableSetOf()
shapeEditorValue.values.forEach { possibleValues.add(InspectableValue.Text(it)) }
}
val identifier = getMetadataId(name, entry.key, false, possibleValues)
fields[identifier] = value
}
return InspectableObject(fields)
}
override fun isArray(array: EditorArray?): Inspectable {
val values = array?.value?.map { value -> toInspectable(name, value) }
return InspectableArray(values ?: listOf())
}
override fun isPick(pick: EditorPick): Inspectable = InspectableValue.Enum(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)
})
}
private fun fromDrawable(d: Drawable?): Inspectable =
when (d) {
is ColorDrawable -> InspectableValue.Color(Color.fromColor(d.color))
else -> InspectableValue.Unknown(d.toString())
}
}

View File

@@ -0,0 +1,432 @@
/*
* 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.common.enumToInspectableSet
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_ATTRIBUTE, NAMESPACE, "background")
private var ForegroundId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "foreground")
private val DirectionId =
MetadataRegister.register(
MetadataRegister.TYPE_ATTRIBUTE,
NAMESPACE,
"direction",
false,
enumToInspectableSet<YogaDirection>())
private val FlexDirectionId =
MetadataRegister.register(
MetadataRegister.TYPE_ATTRIBUTE,
NAMESPACE,
"flexDirection",
false,
enumToInspectableSet<YogaFlexDirection>())
private val JustifyContentId =
MetadataRegister.register(
MetadataRegister.TYPE_ATTRIBUTE,
NAMESPACE,
"justifyContent",
false,
enumToInspectableSet<YogaJustify>())
private val AlignItemsId =
MetadataRegister.register(
MetadataRegister.TYPE_ATTRIBUTE,
NAMESPACE,
"alignItems",
false,
enumToInspectableSet<YogaAlign>())
private val AlignSelfId =
MetadataRegister.register(
MetadataRegister.TYPE_ATTRIBUTE,
NAMESPACE,
"alignSelf",
false,
enumToInspectableSet<YogaAlign>())
private val AlignContentId =
MetadataRegister.register(
MetadataRegister.TYPE_ATTRIBUTE,
NAMESPACE,
"alignContent",
false,
enumToInspectableSet<YogaAlign>())
private val PositionTypeId =
MetadataRegister.register(
MetadataRegister.TYPE_ATTRIBUTE,
NAMESPACE,
"positionType",
false,
enumToInspectableSet<YogaPositionType>())
private val FlexGrowId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "flexGrow")
private val FlexShrinkId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "flexShrink")
private val FlexBasisId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "flexBasis")
private val WidthId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "width")
private val HeightId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "height")
private val MinWidthId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "minWidth")
private val MinHeightId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "minHeight")
private val MaxWidthId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "maxWidth")
private val MaxHeightId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "maxHeight")
private val AspectRatioId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "aspectRatio")
private val MarginId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "margin")
private val PaddingId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "padding")
private val BorderId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "border")
private val PositionId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "position")
private val LeftId = MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "left")
private val TopId = MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "top")
private val RightId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "right")
private val BottomId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "bottom")
private val StartId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "start")
private val EndId = MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "end")
private val HorizontalId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "horizontal")
private val VerticalId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "vertical")
private val AllId = MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "all")
private val HasViewOutputId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "hasViewOutput")
private val AlphaId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "alpha")
private val ScaleId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "scale")
private val RotationId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "rotation")
private val EmptyId = MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "")
private val NoneId = MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "none")
private val SizeId = MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "size")
private val ViewOutputId =
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "viewOutput")
fun getInspectableBox(
left: YogaValue?,
top: YogaValue?,
right: YogaValue?,
bottom: YogaValue?,
horizontal: YogaValue?,
vertical: YogaValue?,
all: YogaValue?,
start: YogaValue?,
end: YogaValue?
): InspectableObject {
val props = mutableMapOf<MetadataId, Inspectable>()
var actualLeft = 0
var actualTop = 0
var actualRight = 0
var actualBottom = 0
all?.let { yogaValue ->
if (yogaValue.unit != YogaUnit.UNDEFINED) {
if (yogaValue.unit == YogaUnit.POINT || yogaValue.unit == YogaUnit.PERCENT) {
val intValue = yogaValue.value.toInt()
actualLeft = intValue
actualTop = intValue
actualRight = intValue
actualBottom = intValue
}
props[AllId] = InspectableValue.Text(yogaValue.toString())
}
}
horizontal?.let { yogaValue ->
if (yogaValue.unit != YogaUnit.UNDEFINED) {
if (yogaValue.unit == YogaUnit.POINT || yogaValue.unit == YogaUnit.PERCENT) {
val intValue = yogaValue.value.toInt()
actualLeft = intValue
actualRight = intValue
}
props[HorizontalId] = InspectableValue.Text(yogaValue.toString())
}
}
vertical?.let { yogaValue ->
if (yogaValue.unit != YogaUnit.UNDEFINED) {
if (yogaValue.unit == YogaUnit.POINT || yogaValue.unit == YogaUnit.PERCENT) {
val intValue = yogaValue.value.toInt()
actualTop = intValue
actualBottom = intValue
}
props[VerticalId] = InspectableValue.Text(yogaValue.toString())
}
}
left?.let { yogaValue ->
if (yogaValue.unit != YogaUnit.UNDEFINED) {
if (yogaValue.unit == YogaUnit.POINT || yogaValue.unit == YogaUnit.PERCENT) {
val intValue = yogaValue.value.toInt()
actualLeft = intValue
}
props[LeftId] = InspectableValue.Text(yogaValue.toString())
}
}
right?.let { yogaValue ->
if (yogaValue.unit != YogaUnit.UNDEFINED) {
if (yogaValue.unit == YogaUnit.POINT || yogaValue.unit == YogaUnit.PERCENT) {
val intValue = yogaValue.value.toInt()
actualRight = intValue
}
props[RightId] = InspectableValue.Text(yogaValue.toString())
}
}
top?.let { yogaValue ->
if (yogaValue.unit != YogaUnit.UNDEFINED) {
if (yogaValue.unit == YogaUnit.POINT || yogaValue.unit == YogaUnit.PERCENT) {
val intValue = yogaValue.value.toInt()
actualTop = intValue
}
props[TopId] = InspectableValue.Text(yogaValue.toString())
}
}
bottom?.let { yogaValue ->
if (yogaValue.unit != YogaUnit.UNDEFINED) {
if (yogaValue.unit == YogaUnit.POINT || yogaValue.unit == YogaUnit.PERCENT) {
val intValue = yogaValue.value.toInt()
actualBottom = intValue
}
props[BottomId] = InspectableValue.Text(yogaValue.toString())
}
}
props[EmptyId] =
InspectableValue.SpaceBox(SpaceBox(actualTop, actualRight, actualBottom, actualLeft))
return InspectableObject(props)
}
fun getInspectableBoxRaw(
left: Float?,
top: Float?,
right: Float?,
bottom: Float?,
horizontal: Float?,
vertical: Float?,
all: Float?,
start: Float?,
end: Float?
): InspectableObject {
val props = mutableMapOf<MetadataId, Inspectable>()
var actualLeft = 0
var actualTop = 0
var actualRight = 0
var actualBottom = 0
all?.let { value ->
if (!value.isNaN()) {
val intValue = value.toInt()
actualLeft = intValue
actualTop = intValue
actualRight = intValue
actualBottom = intValue
props[AllId] = InspectableValue.Number(value)
}
}
horizontal?.let { value ->
if (!value.isNaN()) {
val intValue = value.toInt()
actualLeft = intValue
actualRight = intValue
props[HorizontalId] = InspectableValue.Number(value)
}
}
vertical?.let { value ->
if (!value.isNaN()) {
val intValue = value.toInt()
actualTop = intValue
actualBottom = intValue
props[VerticalId] = InspectableValue.Number(value)
}
}
left?.let { value ->
if (!value.isNaN()) {
val intValue = value.toInt()
actualLeft = intValue
props[LeftId] = InspectableValue.Number(value)
}
}
right?.let { value ->
if (!value.isNaN()) {
val intValue = value.toInt()
actualRight = intValue
props[RightId] = InspectableValue.Number(value)
}
}
top?.let { value ->
if (!value.isNaN()) {
val intValue = value.toInt()
actualTop = intValue
props[TopId] = InspectableValue.Number(value)
}
}
bottom?.let { value ->
if (!value.isNaN()) {
val intValue = value.toInt()
actualBottom = intValue
props[BottomId] = InspectableValue.Number(value)
}
}
props[EmptyId] =
InspectableValue.SpaceBox(SpaceBox(actualTop, actualRight, actualBottom, actualLeft))
return InspectableObject(props)
}
fun getProps(component: DebugComponent): Map<MetadataId, Inspectable> {
val props = mutableMapOf<MetadataId, Inspectable>()
val layout = component.layoutNode ?: return props
props[AlignItemsId] = InspectableValue.Enum(layout.alignItems.name)
props[AlignSelfId] = InspectableValue.Enum(layout.alignSelf.name)
props[AlignContentId] = InspectableValue.Enum(layout.alignContent.name)
props[AspectRatioId] = InspectableValue.Text(layout.aspectRatio.toString())
layout.background?.let { drawable -> props[BackgroundId] = fromDrawable(drawable) }
props[DirectionId] = InspectableValue.Enum(layout.layoutDirection.name)
props[FlexBasisId] = InspectableValue.Text(layout.flexBasis.toString())
props[FlexDirectionId] = InspectableValue.Enum(layout.flexDirection.name)
props[FlexGrowId] = InspectableValue.Text(layout.flexGrow.toString())
props[FlexShrinkId] = InspectableValue.Text(layout.flexShrink.toString())
layout.foreground?.let { drawable -> props[ForegroundId] = fromDrawable(drawable) }
props[JustifyContentId] = InspectableValue.Enum(layout.justifyContent.name)
props[PositionTypeId] = InspectableValue.Enum(layout.positionType.name)
val size: MutableMap<MetadataId, Inspectable> = mutableMapOf()
size[WidthId] = InspectableValue.Text(layout.width.toString())
if (layout.minWidth.unit != YogaUnit.UNDEFINED)
size[MinWidthId] = InspectableValue.Text(layout.minWidth.toString())
if (layout.maxWidth.unit != YogaUnit.UNDEFINED)
size[MaxWidthId] = InspectableValue.Text(layout.maxWidth.toString())
size[HeightId] = InspectableValue.Text(layout.height.toString())
if (layout.minHeight.unit != YogaUnit.UNDEFINED)
size[MinHeightId] = InspectableValue.Text(layout.minHeight.toString())
if (layout.maxHeight.unit != YogaUnit.UNDEFINED)
size[MaxHeightId] = InspectableValue.Text(layout.maxHeight.toString())
props[SizeId] = InspectableObject(size)
props[MarginId] =
getInspectableBox(
layout.getMargin(YogaEdge.LEFT),
layout.getMargin(YogaEdge.TOP),
layout.getMargin(YogaEdge.RIGHT),
layout.getMargin(YogaEdge.BOTTOM),
layout.getMargin(YogaEdge.HORIZONTAL),
layout.getMargin(YogaEdge.VERTICAL),
layout.getMargin(YogaEdge.ALL),
layout.getMargin(YogaEdge.START),
layout.getMargin(YogaEdge.END))
props[PaddingId] =
getInspectableBox(
layout.getPadding(YogaEdge.LEFT),
layout.getPadding(YogaEdge.TOP),
layout.getPadding(YogaEdge.RIGHT),
layout.getPadding(YogaEdge.BOTTOM),
layout.getPadding(YogaEdge.HORIZONTAL),
layout.getPadding(YogaEdge.VERTICAL),
layout.getPadding(YogaEdge.ALL),
layout.getPadding(YogaEdge.START),
layout.getPadding(YogaEdge.END))
props[BorderId] =
getInspectableBoxRaw(
layout.getBorderWidth(YogaEdge.LEFT),
layout.getBorderWidth(YogaEdge.TOP),
layout.getBorderWidth(YogaEdge.RIGHT),
layout.getBorderWidth(YogaEdge.BOTTOM),
layout.getBorderWidth(YogaEdge.HORIZONTAL),
layout.getBorderWidth(YogaEdge.VERTICAL),
layout.getBorderWidth(YogaEdge.ALL),
layout.getBorderWidth(YogaEdge.START),
layout.getBorderWidth(YogaEdge.END))
props[PositionId] =
getInspectableBox(
layout.getPosition(YogaEdge.LEFT),
layout.getPosition(YogaEdge.TOP),
layout.getPosition(YogaEdge.RIGHT),
layout.getPosition(YogaEdge.BOTTOM),
layout.getPosition(YogaEdge.HORIZONTAL),
layout.getPosition(YogaEdge.VERTICAL),
layout.getPosition(YogaEdge.ALL),
layout.getPosition(YogaEdge.START),
layout.getPosition(YogaEdge.END))
val viewOutput: MutableMap<MetadataId, Inspectable> = mutableMapOf()
viewOutput[HasViewOutputId] = InspectableValue.Boolean(layout.hasViewOutput())
if (layout.hasViewOutput()) {
viewOutput[AlphaId] = InspectableValue.Number(layout.alpha)
viewOutput[RotationId] = InspectableValue.Number(layout.rotation)
viewOutput[ScaleId] = InspectableValue.Number(layout.scale)
}
props[ViewOutputId] = InspectableObject(viewOutput)
return props
}
private fun fromDrawable(d: Drawable?): Inspectable =
when (d) {
is ColorDrawable -> InspectableValue.Color(Color.fromColor(d.color))
else -> InspectableValue.Unknown(d.toString())
}
}