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