1 /*
<lambda>null2  * Copyright 2024 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 androidx.xr.compose.subspace.node
18 
19 import androidx.compose.runtime.CompositionLocalMap
20 import androidx.compose.ui.platform.LocalDensity
21 import androidx.compose.ui.semantics.SemanticsConfiguration
22 import androidx.compose.ui.semantics.SemanticsProperties
23 import androidx.compose.ui.unit.Density
24 import androidx.compose.ui.util.fastForEach
25 import androidx.xr.compose.subspace.layout.CoreEntity
26 import androidx.xr.compose.subspace.layout.CoreEntityNode
27 import androidx.xr.compose.subspace.layout.LayoutMeasureScope
28 import androidx.xr.compose.subspace.layout.Measurable
29 import androidx.xr.compose.subspace.layout.MeasurePolicy
30 import androidx.xr.compose.subspace.layout.MeasureResult
31 import androidx.xr.compose.subspace.layout.ParentLayoutParamsAdjustable
32 import androidx.xr.compose.subspace.layout.ParentLayoutParamsModifier
33 import androidx.xr.compose.subspace.layout.Placeable
34 import androidx.xr.compose.subspace.layout.SubspaceLayoutCoordinates
35 import androidx.xr.compose.subspace.layout.SubspaceModifier
36 import androidx.xr.compose.subspace.layout.SubspaceRootMeasurePolicy
37 import androidx.xr.compose.subspace.layout.applyCoreEntityNodes
38 import androidx.xr.compose.unit.IntVolumeSize
39 import androidx.xr.compose.unit.VolumeConstraints
40 import androidx.xr.runtime.math.Pose
41 import androidx.xr.scenecore.Entity
42 import java.util.concurrent.atomic.AtomicInteger
43 
44 private var lastIdentifier = AtomicInteger(0)
45 
46 internal fun generateSemanticsId() = lastIdentifier.incrementAndGet()
47 
48 private val DefaultDensity = Density(1f)
49 
50 /**
51  * An element in the Subspace layout hierarchy (spatial scene graph), built with Compose UI for
52  * subspace.
53  *
54  * This class is based on [androidx.compose.ui.node.LayoutNode].
55  *
56  * TODO(b/330925589): Write unit tests.
57  */
58 internal class SubspaceLayoutNode : ComposeSubspaceNode {
59     /**
60      * The children of this [SubspaceLayoutNode], controlled by [insertAt], [move], and [removeAt].
61      */
62     internal val children: MutableList<SubspaceLayoutNode> = mutableListOf()
63 
64     /** The parent node in the [SubspaceLayoutNode] hierarchy. */
65     internal var parent: SubspaceLayoutNode? = null
66 
67     /** Instance of [MeasurableLayout] to aid with measure/layout phases. */
68     public val measurableLayout: MeasurableLayout = MeasurableLayout()
69 
70     /** The element system [SubspaceOwner]. This value is `null` until [attach] is called */
71     internal var owner: SubspaceOwner? = null
72         private set
73 
74     internal val nodes: SubspaceModifierNodeChain = SubspaceModifierNodeChain(this)
75 
76     override var measurePolicy: MeasurePolicy = ErrorMeasurePolicy
77 
78     override var modifier: SubspaceModifier = SubspaceModifier
79         set(value) {
80             field = value
81             nodes.updateFrom(value)
82         }
83 
84     override var coreEntity: CoreEntity? = null
85         set(value) {
86             check(field == null) { "overwriting non-null CoreEntity is not supported" }
87             field = value
88             if (value != null) {
89                 value.layout = this
90             }
91         }
92 
93     override var compositionLocalMap: CompositionLocalMap = CompositionLocalMap.Empty
94         set(value) {
95             field = value
96             density = value[LocalDensity]
97         }
98 
99     internal var density: Density = DefaultDensity
100         private set
101 
102     /**
103      * This function sets up CoreEntity parent/child relationships that reflect the parent/child
104      * relationships of the corresponding SubspaceLayoutNodes. This should be called any time the
105      * `parent` or `coreEntity` fields are updated.
106      */
107     private fun syncCoreEntityHierarchy() {
108         coreEntity?.parent = findCoreEntityParent(this)
109     }
110 
111     /** Inserts a child [SubspaceLayoutNode] at the given [index]. */
112     internal fun insertAt(index: Int, instance: SubspaceLayoutNode) {
113         check(instance.parent == null) {
114             "Cannot insert $instance because it already has a parent." +
115                 " This tree: " +
116                 debugTreeToString() +
117                 " Parent tree: " +
118                 parent?.debugTreeToString()
119         }
120         check(instance.owner == null) {
121             "Cannot insert $instance because it already has an owner." +
122                 " This tree: " +
123                 debugTreeToString() +
124                 " Other tree: " +
125                 instance.debugTreeToString()
126         }
127 
128         instance.parent = this
129         children.add(index, instance)
130 
131         owner?.let { instance.attach(it) }
132     }
133 
134     /**
135      * Moves [count] elements starting at index [from] to index [to].
136      *
137      * The [to] index is related to the position before the change, so, for example, to move an
138      * element at position 1 to after the element at position 2, [from] should be `1` and [to]
139      * should be `3`. If the elements were [SubspaceLayoutNode] instances, A B C D E, calling
140      * `move(1, 3, 1)` would result in the nodes being reordered to A C B D E.
141      */
142     internal fun move(from: Int, to: Int, count: Int) {
143         if (from == to) {
144             return // nothing to do
145         }
146 
147         for (i in 0 until count) {
148             // if "from" is after "to," the from index moves because we're inserting before it
149             val fromIndex = if (from > to) from + i else from
150             val toIndex = if (from > to) to + i else to + count - 2
151             val child = children.removeAt(fromIndex)
152 
153             children.add(toIndex, child)
154         }
155     }
156 
157     /** Removes one or more children, starting at [index]. */
158     internal fun removeAt(index: Int, count: Int) {
159         require(count >= 0) { "count ($count) must be greater than 0." }
160 
161         for (i in index + count - 1 downTo index) {
162             onChildRemoved(children[i])
163         }
164 
165         children.removeAll(children.subList(index, index + count))
166     }
167 
168     /** Removes all children nodes. */
169     internal fun removeAll() {
170         children.reversed().forEach { child -> onChildRemoved(child) }
171 
172         children.clear()
173     }
174 
175     /** Called when the [child] node is removed from this [SubspaceLayoutNode] hierarchy. */
176     private fun onChildRemoved(child: SubspaceLayoutNode) {
177         owner?.let { child.detach() }
178         child.parent = null
179     }
180 
181     /**
182      * Sets the [SubspaceOwner] of this node.
183      *
184      * This SubspaceLayoutNode must not already be attached and [subspaceOwner] must match the
185      * [parent]'s [subspaceOwner].
186      */
187     internal fun attach(subspaceOwner: SubspaceOwner) {
188         check(this.owner == null) {
189             "Cannot attach $this as it already is attached. Tree: " + debugTreeToString()
190         }
191         check(parent == null || parent?.owner == subspaceOwner) {
192             "Attaching to a different owner($subspaceOwner) than the parent's owner" +
193                 "(${parent?.owner})." +
194                 " This tree: " +
195                 debugTreeToString() +
196                 " Parent tree: " +
197                 parent?.debugTreeToString()
198         }
199 
200         this.owner = subspaceOwner
201 
202         subspaceOwner.onAttach(this)
203         syncCoreEntityHierarchy()
204 
205         nodes.markAsAttached()
206         children.forEach { child -> child.attach(subspaceOwner) }
207         nodes.runOnAttach()
208     }
209 
210     /**
211      * Detaches this node from the [owner].
212      *
213      * The [owner] must not be `null` when this method is called.
214      *
215      * This will also [detach] all children. After executing, the [owner] will be `null`.
216      */
217     internal fun detach() {
218         val owner = owner
219 
220         checkNotNull(owner) {
221             "Cannot detach node that is already detached!  Tree: " + parent?.debugTreeToString()
222         }
223 
224         nodes.runOnDetach()
225         children.forEach { child -> child.detach() }
226         nodes.markAsDetached()
227 
228         owner.onDetach(this)
229         this.owner = null
230     }
231 
232     internal fun requestRelayout() {
233         owner?.requestRelayout()
234     }
235 
236     override fun toString(): String {
237         return measurableLayout.config.getOrElse(SemanticsProperties.TestTag) { super.toString() }
238     }
239 
240     /** Call this method to see a dump of the SpatialLayoutNode tree structure. */
241     @Suppress("unused")
242     internal fun debugTreeToString(depth: Int = 0): String {
243         val tree = StringBuilder()
244         val depthString = "  ".repeat(depth)
245         tree.append("$depthString|-${toString()}\n")
246 
247         var currentNode: SubspaceModifier.Node? = nodes.head
248         while (currentNode != null && currentNode != nodes.tail) {
249             tree.append("$depthString  *-$currentNode\n")
250             currentNode = currentNode.child
251         }
252 
253         children.forEach { child -> tree.append(child.debugTreeToString(depth + 1)) }
254 
255         var treeString = tree.toString()
256         if (depth == 0) {
257             // Delete trailing newline
258             treeString = treeString.substring(0, treeString.length - 1)
259         }
260 
261         return treeString
262     }
263 
264     /** Call this method to see a dump of the Jetpack XR node hierarchy. */
265     @Suppress("unused")
266     internal fun debugEntityTreeToString(depth: Int = 0): String {
267         val tree = StringBuilder()
268         val depthString = "  ".repeat(depth)
269         var nextDepth = depth
270         if (coreEntity != null) {
271             tree.append(
272                 "$depthString|-${coreEntity?.entity} -> ${findCoreEntityParent(this)?.entity}\n"
273             )
274             nextDepth++
275         }
276 
277         children.forEach { child -> tree.append(child.debugEntityTreeToString(nextDepth)) }
278 
279         var treeString = tree.toString()
280         if (depth == 0 && treeString.isNotEmpty()) {
281             // Delete trailing newline
282             treeString = treeString.substring(0, treeString.length - 1)
283         }
284 
285         return treeString
286     }
287 
288     /**
289      * A [Measurable] and [Placeable] object that is used to measure and lay out the children of
290      * this node.
291      *
292      * See [androidx.compose.ui.node.NodeCoordinator]
293      */
294     public inner class MeasurableLayout :
295         Measurable, SubspaceLayoutCoordinates, SubspaceSemanticsInfo, Placeable() {
296         private var layoutPose: Pose? = null
297         private var measureResult: MeasureResult? = null
298 
299         /** Unique ID used by semantics libraries. */
300         public override val semanticsId: Int = generateSemanticsId()
301 
302         /**
303          * The tail node of [SubspaceModifierNodeChain].
304          *
305          * This node is used to mark the end of the modifier chain.
306          */
307         public val tail: SubspaceModifier.Node = TailModifierNode()
308 
309         override fun measure(constraints: VolumeConstraints): Placeable =
310             nodes.measureChain(constraints, ::measureJustThis)
311 
312         override fun adjustParams(params: ParentLayoutParamsAdjustable) {
313             nodes.getAll<ParentLayoutParamsModifier>().forEach { it.adjustParams(params) }
314         }
315 
316         private fun measureJustThis(constraints: VolumeConstraints): Placeable {
317             measureResult =
318                 with(measurePolicy) {
319                     LayoutMeasureScope(this@SubspaceLayoutNode)
320                         .measure(
321                             this@SubspaceLayoutNode.children.map { it.measurableLayout }.toList(),
322                             constraints,
323                         )
324                 }
325 
326             measuredWidth = measureResult!!.width
327             measuredHeight = measureResult!!.height
328             measuredDepth = measureResult!!.depth
329 
330             return this
331         }
332 
333         /**
334          * Places the children of this node at the given pose.
335          *
336          * @param pose The pose to place the children at, with translation in pixels.
337          */
338         public override fun placeAt(pose: Pose) {
339             layoutPose = pose
340 
341             coreEntity?.applyCoreEntityNodes(nodes.getAll<CoreEntityNode>())
342             coreEntity?.updateEntityPose()
343             coreEntity?.size = IntVolumeSize(measuredWidth, measuredHeight, measuredDepth)
344 
345             measureResult?.placeChildren(
346                 object : PlacementScope() {
347                     override val coordinates = this@MeasurableLayout
348                 }
349             )
350 
351             // Call coordinates-aware callbacks after the node and its children are placed.
352             nodes.getAll<LayoutCoordinatesAwareModifierNode>().forEach {
353                 it.onLayoutCoordinates(this)
354             }
355         }
356 
357         override val pose: Pose
358             get() = layoutPose ?: Pose.Identity
359 
360         /** The position of this node relative to the root of this Compose hierarchy, in pixels. */
361         override val poseInRoot: Pose
362             get() =
363                 coordinatesInRoot?.poseInRoot?.let {
364                     pose.translate(it.translation).rotate(it.rotation)
365                 } ?: pose
366 
367         override val poseInParentEntity: Pose
368             get() =
369                 coordinatesInParentEntity?.poseInParentEntity?.let {
370                     pose.translate(it.translation).rotate(it.rotation)
371                 } ?: pose
372 
373         public override val semanticsChildren: MutableList<SubspaceSemanticsInfo>
374             get() {
375                 val list: MutableList<SubspaceSemanticsInfo> = mutableListOf()
376                 fillOneLayerOfSemanticsWrappers(list)
377                 return list
378             }
379 
380         public override val semanticsParent: SubspaceSemanticsInfo?
381             get() {
382                 var node: SubspaceLayoutNode? = parent
383                 while (node != null) {
384                     if (node.hasSemantics) {
385                         return node.measurableLayout
386                     }
387                     node = node.parent
388                 }
389                 return null
390             }
391 
392         private fun SubspaceLayoutNode.fillOneLayerOfSemanticsWrappers(
393             list: MutableList<SubspaceSemanticsInfo>
394         ) {
395             children.fastForEach { child ->
396                 if (child.hasSemantics) {
397                     list.add(child.measurableLayout)
398                 } else {
399                     child.fillOneLayerOfSemanticsWrappers(list)
400                 }
401             }
402         }
403 
404         private val SubspaceLayoutNode.hasSemantics: Boolean
405             get() = nodes.getLast<SubspaceSemanticsModifierNode>() != null
406 
407         public override val semanticsEntity: Entity?
408             get() = coreEntity?.entity
409 
410         /**
411          * The layout coordinates of the parent [SubspaceLayoutNode] up to the root of the hierarchy
412          * including application from any [SubspaceLayoutModifierNode] instances applied to this
413          * node.
414          *
415          * This applies the layout changes of all [SubspaceLayoutModifierNode] instances in the
416          * modifier chain and then [parentCoordinatesInRoot] or just [parentCoordinatesInRoot] if no
417          * [SubspaceLayoutModifierNode] is present.
418          */
419         private val coordinatesInRoot: SubspaceLayoutCoordinates?
420             get() =
421                 nodes.getLast<SubspaceLayoutModifierNode>()?.requireCoordinator()
422                     ?: parentCoordinatesInRoot
423 
424         /** Traverse the parent hierarchy up to the root. */
425         internal val parentCoordinatesInRoot: SubspaceLayoutCoordinates?
426             get() = parent?.measurableLayout
427 
428         /**
429          * The layout coordinates up to the nearest parent [CoreEntity] including mutations from any
430          * [SubspaceLayoutModifierNode] instances applied to this node.
431          *
432          * This applies the layout changes of all [SubspaceLayoutModifierNode] instances in the
433          * modifier chain and then [parentCoordinatesInParentEntity] or just
434          * [parentCoordinatesInParentEntity] if no [SubspaceLayoutModifierNode] is present.
435          */
436         private val coordinatesInParentEntity: SubspaceLayoutCoordinates?
437             get() =
438                 nodes.getLast<SubspaceLayoutModifierNode>()?.requireCoordinator()
439                     ?: parentCoordinatesInParentEntity
440 
441         /** Traverse up the parent hierarchy until we reach a node with an entity. */
442         internal val parentCoordinatesInParentEntity: SubspaceLayoutCoordinates?
443             get() = if (parent?.coreEntity == null) parent?.measurableLayout else null
444 
445         override val size: IntVolumeSize
446             get() {
447                 return coreEntity?.size
448                     ?: IntVolumeSize(measuredWidth, measuredHeight, measuredDepth)
449             }
450 
451         override fun toString(): String {
452             return this@SubspaceLayoutNode.toString()
453         }
454 
455         /**
456          * The semantics configuration of this node.
457          *
458          * This includes all properties attached as modifiers to the current layout node.
459          */
460         public override val config: SemanticsConfiguration
461             get() {
462                 val config = SemanticsConfiguration()
463                 nodes.getAll<SubspaceSemanticsModifierNode>().forEach {
464                     with(config) { with(it) { applySemantics() } }
465                 }
466                 return config
467             }
468     }
469 
470     /** Companion object for [SubspaceLayoutNode]. */
471     public companion object {
472         private val ErrorMeasurePolicy: MeasurePolicy = MeasurePolicy { _, _ ->
473             error("Undefined measure and it is required")
474         }
475 
476         /**
477          * A [MeasurePolicy] that is used for the root node of the Subspace layout hierarchy.
478          *
479          * Note: Root node itself has no size outside its children.
480          */
481         public val RootMeasurePolicy: MeasurePolicy = SubspaceRootMeasurePolicy()
482 
483         /** A constructor that creates a new [SubspaceLayoutNode]. */
484         public val Constructor: () -> SubspaceLayoutNode = { SubspaceLayoutNode() }
485 
486         /** Walk up the parent hierarchy to find the closest ancestor attached to a [CoreEntity]. */
487         private fun findCoreEntityParent(node: SubspaceLayoutNode) =
488             generateSequence(node.parent) { it.parent }.firstNotNullOfOrNull { it.coreEntity }
489     }
490 }
491 
492 internal class TailModifierNode : SubspaceModifier.Node() {
toStringnull493     override fun toString(): String {
494         return "<tail>"
495     }
496 }
497