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.spatial 18 19 import android.view.View 20 import androidx.compose.animation.core.FiniteAnimationSpec 21 import androidx.compose.animation.core.Transition 22 import androidx.compose.animation.core.animateDp 23 import androidx.compose.animation.core.spring 24 import androidx.compose.animation.core.updateTransition 25 import androidx.compose.foundation.layout.Box 26 import androidx.compose.foundation.shape.ZeroCornerSize 27 import androidx.compose.runtime.Composable 28 import androidx.compose.runtime.CompositionLocalProvider 29 import androidx.compose.runtime.DisposableEffect 30 import androidx.compose.runtime.LaunchedEffect 31 import androidx.compose.runtime.getValue 32 import androidx.compose.runtime.mutableStateOf 33 import androidx.compose.runtime.remember 34 import androidx.compose.runtime.setValue 35 import androidx.compose.ui.Modifier 36 import androidx.compose.ui.draw.alpha 37 import androidx.compose.ui.geometry.Offset 38 import androidx.compose.ui.platform.LocalView 39 import androidx.compose.ui.unit.Dp 40 import androidx.compose.ui.unit.IntSize 41 import androidx.xr.compose.platform.LocalCoreEntity 42 import androidx.xr.compose.platform.LocalOpaqueEntity 43 import androidx.xr.compose.platform.LocalSession 44 import androidx.xr.compose.platform.coreMainPanelEntity 45 import androidx.xr.compose.subspace.layout.SpatialRoundedCornerShape 46 import androidx.xr.compose.subspace.layout.SpatialShape 47 import androidx.xr.compose.subspace.rememberComposeView 48 import androidx.xr.compose.subspace.rememberCorePanelEntity 49 import androidx.xr.compose.unit.IntVolumeSize 50 import androidx.xr.compose.unit.Meter 51 import androidx.xr.compose.unit.Meter.Companion.meters 52 import androidx.xr.runtime.math.Pose 53 import androidx.xr.runtime.math.Vector3 54 import androidx.xr.scenecore.PanelEntity 55 import androidx.xr.scenecore.PixelDimensions 56 57 internal object ElevatedPanelDefaults { 58 /** Default shape for a Spatial Panel. */ 59 internal val shape: SpatialShape = SpatialRoundedCornerShape(ZeroCornerSize) 60 } 61 62 /** 63 * This is the base panel underlying the implementations of SpatialElevation, SpatialPopup, and 64 * SpatialDialog. It allows creating a panel at a specific size and offset. 65 */ 66 @Composable 67 internal fun ElevatedPanel( 68 spatialElevationLevel: SpatialElevationLevel, 69 contentSize: IntSize, 70 shape: SpatialShape = ElevatedPanelDefaults.shape, 71 contentOffset: Offset? = null, 72 transitionSpec: 73 @Composable 74 Transition.Segment<SpatialElevationLevel>.() -> FiniteAnimationSpec<Dp> = 75 { 76 spring() 77 }, 78 content: @Composable () -> Unit, 79 ) { 80 val parentView = LocalView.current 81 val zDepth by 82 updateTransition(targetState = spatialElevationLevel, label = "restingLevelTransition") statenull83 .animateDp(transitionSpec = transitionSpec, label = "zDepth") { state -> state.level } <lambda>null84 var parentViewSize by remember { mutableStateOf(parentView.size) } <lambda>null85 DisposableEffect(parentView) { 86 val listener = 87 View.OnLayoutChangeListener { _, _, _, right, bottom, _, _, _, _ -> 88 parentViewSize = IntSize(right, bottom) 89 } 90 parentView.addOnLayoutChangeListener(listener) 91 onDispose { parentView.removeOnLayoutChangeListener(listener) } 92 } 93 94 ElevatedPanel( 95 contentSize = contentSize, 96 shape = shape, 97 pose = <lambda>null98 contentOffset?.let { rememberCalculatePose(it, parentViewSize, contentSize, zDepth) }, 99 content = content, 100 ) 101 } 102 103 /** 104 * This is the base panel underlying the implementations of SpatialElevation, SpatialPopup, and 105 * SpatialDialog. It allows creating a panel at a specific size and [Pose]. 106 */ 107 @Composable 108 internal fun ElevatedPanel( 109 contentSize: IntSize, 110 shape: SpatialShape = ElevatedPanelDefaults.shape, 111 pose: Pose? = null, 112 content: @Composable () -> Unit, 113 ) { <lambda>null114 val session = checkNotNull(LocalSession.current) { "session must be initialized" } 115 val parentEntity = LocalCoreEntity.current ?: session.coreMainPanelEntity 116 val view = rememberComposeView() 117 val panelEntity = <lambda>null118 rememberCorePanelEntity(shape = shape) { 119 PanelEntity.create( 120 session = session, 121 view = view, 122 pixelDimensions = contentSize.run { PixelDimensions(width, height) }, 123 name = "ElevatedPanel:${view.id}", 124 ) 125 } 126 <lambda>null127 view.setContent { 128 CompositionLocalProvider(LocalOpaqueEntity provides panelEntity) { 129 Box(Modifier.alpha(if (pose == null) 0.0f else 1.0f)) { content() } 130 } 131 } 132 <lambda>null133 LaunchedEffect(pose) { 134 if (pose != null) { 135 panelEntity.entity.setPose(pose) 136 } 137 } 138 <lambda>null139 LaunchedEffect(contentSize) { 140 val width = contentSize.width 141 val height = contentSize.height 142 143 panelEntity.size = IntVolumeSize(width = width, height = height, depth = 0) 144 } 145 <lambda>null146 LaunchedEffect(parentEntity) { panelEntity.entity.setParent(parentEntity.entity) } 147 } 148 149 /** A 3D vector where each coordinate is [Meter]s. */ 150 internal data class MeterPosition( 151 val x: Meter = 0.meters, 152 val y: Meter = 0.meters, 153 val z: Meter = 0.meters, 154 ) { 155 /** 156 * Adds this [MeterPosition] to the [other] one. 157 * 158 * @param other the other [MeterPosition] to add. 159 * @return a new [MeterPosition] representing the sum of the two positions. 160 */ plusnull161 public operator fun plus(other: MeterPosition) = 162 MeterPosition(x = x + other.x, y = y + other.y, z = z + other.z) 163 164 fun toVector3() = Vector3(x = x.toM(), y = y.toM(), z = z.toM()) 165 } 166 167 internal val View.size 168 get() = IntSize(width, height) 169