• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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]. */
MotionValueDebuggerStatenull36 fun MotionValueDebuggerState(): MotionValueDebuggerState {
37     return MotionValueDebuggerStateImpl()
38 }
39 
40 /** Collector for [MotionValue]s in the Node subtree that should be observed for debug purposes. */
motionValueDebuggernull41 fun 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. */
DelegatableNodenull53 fun DelegatableNode.findMotionValueDebugger(): MotionValueDebugger? {
54     return findNearestAncestor(TRAVERSAL_NODE_KEY) as? MotionValueDebugger
55 }
56 
57 /** Registers the motion value for debugging with the parent [MotionValue]. */
debugMotionValuenull58 fun 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