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