1 /* 2 * Copyright (C) 2025 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.mechanics.debug 18 19 import androidx.compose.runtime.mutableStateListOf 20 import androidx.compose.ui.Modifier 21 import androidx.compose.ui.node.DelegatableNode 22 import androidx.compose.ui.node.ModifierNodeElement 23 import androidx.compose.ui.node.TraversableNode 24 import androidx.compose.ui.node.findNearestAncestor 25 import androidx.compose.ui.platform.InspectorInfo 26 import com.android.mechanics.MotionValue 27 import com.android.mechanics.debug.MotionValueDebuggerNode.Companion.TRAVERSAL_NODE_KEY 28 import kotlinx.coroutines.DisposableHandle 29 30 /** State for the [MotionValueDebugger]. */ 31 sealed interface MotionValueDebuggerState { 32 val observedMotionValues: List<MotionValue> 33 } 34 35 /** Factory for [MotionValueDebugger]. */ MotionValueDebuggerStatenull36fun MotionValueDebuggerState(): MotionValueDebuggerState { 37 return MotionValueDebuggerStateImpl() 38 } 39 40 /** Collector for [MotionValue]s in the Node subtree that should be observed for debug purposes. */ motionValueDebuggernull41fun Modifier.motionValueDebugger(state: MotionValueDebuggerState): Modifier = 42 this.then(MotionValueDebuggerElement(state as MotionValueDebuggerStateImpl)) 43 44 /** 45 * [motionValueDebugger]'s interface, nodes in the subtree of a [motionValueDebugger] can retrieve 46 * it using [findMotionValueDebugger]. 47 */ 48 sealed interface MotionValueDebugger { 49 fun register(motionValue: MotionValue): DisposableHandle 50 } 51 52 /** Finds a [MotionValueDebugger] that was registered via a [motionValueDebugger] modifier. */ DelegatableNodenull53fun DelegatableNode.findMotionValueDebugger(): MotionValueDebugger? { 54 return findNearestAncestor(TRAVERSAL_NODE_KEY) as? MotionValueDebugger 55 } 56 57 /** Registers the motion value for debugging with the parent [MotionValue]. */ debugMotionValuenull58fun Modifier.debugMotionValue(motionValue: MotionValue): Modifier = 59 this.then(DebugMotionValueElement(motionValue)) 60 61 internal class MotionValueDebuggerNode(internal var state: MotionValueDebuggerStateImpl) : 62 Modifier.Node(), TraversableNode, MotionValueDebugger { 63 64 override val traverseKey = TRAVERSAL_NODE_KEY 65 66 override fun register(motionValue: MotionValue): DisposableHandle { 67 val state = state 68 state.observedMotionValues.add(motionValue) 69 return DisposableHandle { state.observedMotionValues.remove(motionValue) } 70 } 71 72 companion object { 73 const val TRAVERSAL_NODE_KEY = "com.android.mechanics.debug.DEBUG_CONNECTOR_NODE_KEY" 74 } 75 } 76 77 private data class MotionValueDebuggerElement(val state: MotionValueDebuggerStateImpl) : 78 ModifierNodeElement<MotionValueDebuggerNode>() { createnull79 override fun create(): MotionValueDebuggerNode = MotionValueDebuggerNode(state) 80 81 override fun InspectorInfo.inspectableProperties() { 82 // Intentionally empty 83 } 84 updatenull85 override fun update(node: MotionValueDebuggerNode) { 86 check(node.state === state) 87 } 88 } 89 90 internal class DebugMotionValueNode(motionValue: MotionValue) : Modifier.Node() { 91 92 private var debugger: MotionValueDebugger? = null 93 94 internal var motionValue = motionValue 95 set(value) { 96 registration?.dispose() 97 registration = debugger?.register(value) 98 field = value 99 } 100 101 internal var registration: DisposableHandle? = null 102 onAttachnull103 override fun onAttach() { 104 debugger = findMotionValueDebugger() 105 registration = debugger?.register(motionValue) 106 } 107 onDetachnull108 override fun onDetach() { 109 debugger = null 110 registration?.dispose() 111 registration = null 112 } 113 } 114 115 private data class DebugMotionValueElement(val motionValue: MotionValue) : 116 ModifierNodeElement<DebugMotionValueNode>() { createnull117 override fun create(): DebugMotionValueNode = DebugMotionValueNode(motionValue) 118 119 override fun InspectorInfo.inspectableProperties() { 120 // Intentionally empty 121 } 122 updatenull123 override fun update(node: DebugMotionValueNode) { 124 node.motionValue = motionValue 125 } 126 } 127 128 internal class MotionValueDebuggerStateImpl : MotionValueDebuggerState { 129 override val observedMotionValues: MutableList<MotionValue> = mutableStateListOf() 130 } 131