All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.androidx.constraintlayout.compose.ToolingUtils.kt Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.constraintlayout.compose

import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.semantics.SemanticsPropertyKey
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
import androidx.constraintlayout.core.state.State.Companion.PARENT
import androidx.constraintlayout.core.widgets.ConstraintWidget
import androidx.constraintlayout.core.widgets.ConstraintWidgetContainer
import androidx.constraintlayout.core.widgets.HelperWidget
import org.json.JsonArray
import org.json.JsonObject

/**
 * [SemanticsPropertyKey] to test [DesignInfoProvider]
 */
val DesignInfoDataKey = SemanticsPropertyKey("DesignInfoProvider")

/**
 * [SemanticsPropertyReceiver] to test [DesignInfoProvider]
 */
@PublishedApi
internal var SemanticsPropertyReceiver.designInfoProvider by DesignInfoDataKey

/**
 * Interface used for Studio tooling.
 *
 * Returns a json string with the constraints and bounding box for each ID in the system.
 */
fun interface DesignInfoProvider {
    fun getDesignInfo(startX: Int, startY: Int, args: String): String
}

private const val CONSTRAINTS_JSON_VERSION = 1

internal fun parseConstraintsToJson(
    root: ConstraintWidgetContainer,
    state: State,
    startX: Int,
    startY: Int
): String {
    // TODO: Take arguments to filter specific information, eg: "BOUNDS_ONLY" would remove
    //  'constraints' and 'helperReferences' from the json
    // TODO: Add information on the render-time transforms, eg: transforms: { rotationZ: 10 }
    // The root id is not user defined, so we create one
    val rootId = PARENT.toString()
    val idToConstraintsJson = JsonObject()
    root.children.forEach { constraintWidget ->
        val constraintsInfoArray = JsonArray()
        val helperReferences = mutableListOf()
        val isHelper = constraintWidget is HelperWidget
        val widgetId = constraintWidget.stringId ?: ""

        if (isHelper) {
            addReferencesIds(constraintWidget as HelperWidget, helperReferences, root, rootId)
        }

        constraintWidget.anchors.forEach { anchor ->
            if (anchor?.isConnected == true) {
                val targetWidget = anchor.target?.owner
                val targetIsParent = root == targetWidget
                val targetIsHelper = targetWidget is HelperWidget
                val targetId = when {
                    targetIsParent -> rootId
                    targetIsHelper -> targetWidget?.getHelperId(state)
                    else -> targetWidget.getRefId()
                }
                constraintsInfoArray.put(
                    JsonObject()
                        .put("originAnchor", anchor.type)
                        .put("targetAnchor", anchor.target!!.type)
                        .put("target", targetId)
                        .put("margin", anchor.margin)
                )
            }
        }

        idToConstraintsJson.putViewIdToBoundsAndConstraints(
            viewId = widgetId,
            boxJson = constraintWidget.boundsToJson(startX, startY),
            isHelper = constraintWidget is HelperWidget,
            isRoot = false,
            helperReferences = helperReferences,
            constraintsInfoArray = constraintsInfoArray
        )
    }
    idToConstraintsJson.putViewIdToBoundsAndConstraints(
        viewId = rootId,
        boxJson = root.boundsToJson(startX, startY),
        isHelper = false,
        isRoot = true,
        helperReferences = emptyList(),
        constraintsInfoArray = JsonArray()
    )
    return createDesignInfoJson(idToConstraintsJson)
}

private fun addReferencesIds(
    helperWidget: HelperWidget,
    helperReferences: MutableList,
    root: ConstraintWidgetContainer,
    rootId: String
) {
    for (i in 0 until helperWidget.mWidgetsCount) {
        val referencedWidget = helperWidget.mWidgets[i]
        val referenceId = if (referencedWidget == root) rootId else referencedWidget.getRefId()
        helperReferences.add(referenceId)
    }
}

/**
 * Returns the Id used for HelperWidgets like barriers or guidelines. Blank if there's no Id.
 */
private fun ConstraintWidget.getHelperId(state: State): String =
    state.getKeyId(this as HelperWidget).toString()

/**
 * Returns the Id used for Composables within the layout. Blank if there's no Id.
 */
private fun ConstraintWidget?.getRefId(): String =
    (this?.companionWidget as? Measurable)?.layoutId?.toString() ?: this?.stringId.toString()

private fun createDesignInfoJson(content: JsonObject) = JsonObject()
    .put("type", "CONSTRAINTS")
    .put("version", CONSTRAINTS_JSON_VERSION)
    .put("content", content).toString()

private fun ConstraintWidget.boundsToJson(startX: Int, startY: Int) = JsonObject()
    .put("left", left + startX)
    .put("top", top + startY)
    .put("right", right + startX)
    .put("bottom", bottom + startY)

private fun JsonObject.putViewIdToBoundsAndConstraints(
    viewId: String,
    boxJson: JsonObject,
    isHelper: Boolean,
    isRoot: Boolean,
    helperReferences: List,
    constraintsInfoArray: JsonArray
) {
    val viewWithBoundsAndConstraints = JsonObject()
    viewWithBoundsAndConstraints.put("viewId", viewId)
    viewWithBoundsAndConstraints.put("box", boxJson)
    viewWithBoundsAndConstraints.put("isHelper", isHelper)
    viewWithBoundsAndConstraints.put("isRoot", isRoot)

    val helperReferencesArray = JsonArray()
    helperReferences.forEach(helperReferencesArray::put)
    viewWithBoundsAndConstraints.put("helperReferences", helperReferencesArray)

    viewWithBoundsAndConstraints.put("constraints", constraintsInfoArray)
    put(viewId, viewWithBoundsAndConstraints)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy