diff --git a/gradle.properties b/gradle.properties index 0346ade7c..986acdb4e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -26,4 +26,4 @@ android.enableR8.fullMode=true # Use an AndroidX snapshot build. # https://androidx.dev/snapshots/builds -# snapshotVersion=14793336 +snapshotVersion=15378830 diff --git a/xr/build.gradle.kts b/xr/build.gradle.kts index 20c66bdcb..f19eb1a7b 100644 --- a/xr/build.gradle.kts +++ b/xr/build.gradle.kts @@ -46,7 +46,8 @@ val isUsingSnapshot = snapshotVersion != null val implementationXrLibraries = listOf>( xrLibs.androidx.xr.arcore.asProvider(), xrLibs.androidx.xr.arcore.play.services, - xrLibs.androidx.xr.glimmer, + xrLibs.androidx.xr.glimmer.asProvider(), + xrLibs.androidx.xr.glimmer.googlefonts, xrLibs.androidx.xr.projected.asProvider(), xrLibs.androidx.xr.scenecore, xrLibs.androidx.xr.compose, diff --git a/xr/libs.versions.toml b/xr/libs.versions.toml index a4589f9a9..3b82658f9 100644 --- a/xr/libs.versions.toml +++ b/xr/libs.versions.toml @@ -11,6 +11,7 @@ androidx-xr-arcore = { module = "androidx.xr.arcore:arcore", version.ref = "andr androidx-xr-arcore-play-services = { module = "androidx.xr.arcore:arcore-play-services", version.ref = "androidx-xr-arcore-play-services" } androidx-xr-compose = { module = "androidx.xr.compose:compose", version.ref = "androidx-xr-compose" } androidx-xr-glimmer = { module = "androidx.xr.glimmer:glimmer", version.ref = "androidx-xr-glimmer" } +androidx-xr-glimmer-googlefonts = { module = "androidx.xr.glimmer:glimmer-google-fonts", version.ref = "androidx-xr-glimmer" } androidx-xr-projected = { module = "androidx.xr.projected:projected", version.ref = "androidx-xr-projected" } androidx-xr-projected-testing = { module = "androidx.xr.projected:projected-testing", version.ref = "androidx-xr-projected" } androidx-xr-scenecore = { module = "androidx.xr.scenecore:scenecore", version.ref = "androidx-xr-scenecore" } diff --git a/xr/src/main/java/com/example/xr/arcore/DepthMaps.kt b/xr/src/main/java/com/example/xr/arcore/DepthMaps.kt index 34628f7f1..1f61075ef 100644 --- a/xr/src/main/java/com/example/xr/arcore/DepthMaps.kt +++ b/xr/src/main/java/com/example/xr/arcore/DepthMaps.kt @@ -16,10 +16,14 @@ package com.example.xr.arcore -import androidx.xr.arcore.DepthMap +import android.content.Context +import androidx.xr.arcore.Depth import androidx.xr.runtime.DepthEstimationMode +import androidx.xr.runtime.ExperimentalXrDeviceLifecycleApi +import androidx.xr.runtime.RenderingMode import androidx.xr.runtime.Session import androidx.xr.runtime.SessionConfigureSuccess +import androidx.xr.runtime.XrDevice private fun configureDepthEstimation(session: Session) { // [START androidxr_arcore_depthmaps_configure] @@ -34,12 +38,25 @@ private fun configureDepthEstimation(session: Session) { // [END androidxr_arcore_depthmaps_configure] } +@OptIn(ExperimentalXrDeviceLifecycleApi::class) +private fun checkLeftDepthSupport(context: Context) { + // [START androidxr_arcore_depthmaps_check_support] + val xrDevice = XrDevice.getCurrentDevice(context) + val hasMonoDepth = xrDevice.isRenderingModeSupported(RenderingMode.MONO) + val hasStereoDepth = xrDevice.isRenderingModeSupported(RenderingMode.STEREO) + // [END androidxr_arcore_depthmaps_check_support] +} + @Suppress("UnusedVariable") -private suspend fun getDepthMap(session: Session) { +private fun getDepthMap(session: Session, hasStereoDepth: Boolean) { // [START androidxr_arcore_depthmaps_obtain_depth_map] - val depthMap = DepthMap.left(session) ?: return + if (hasStereoDepth) { + val depthMap = Depth.left(session) + } // [END androidxr_arcore_depthmaps_obtain_depth_map] +} +private suspend fun calculateDepthResults(depthMap: Depth) { // [START androidxr_arcore_depthmaps_calculate_results] depthMap.state.collect { depthMap -> // example coordinates diff --git a/xr/src/main/java/com/example/xr/arcore/DevicePose.kt b/xr/src/main/java/com/example/xr/arcore/DevicePose.kt index e47357b45..0bfd85321 100644 --- a/xr/src/main/java/com/example/xr/arcore/DevicePose.kt +++ b/xr/src/main/java/com/example/xr/arcore/DevicePose.kt @@ -26,7 +26,7 @@ private fun configureDevicePose(session: Session) { // [START androidxr_arcore_device_pose_configure] // Define the configuration object to enable tracking device pose. val newConfig = session.config.copy( - deviceTracking = DeviceTrackingMode.SPATIAL_LAST_KNOWN + deviceTracking = DeviceTrackingMode.SPATIAL ) // Apply the configuration to the session. try { diff --git a/xr/src/main/java/com/example/xr/arcore/FaceTracking.kt b/xr/src/main/java/com/example/xr/arcore/FaceTracking.kt index 4533d3e64..4e6e65346 100644 --- a/xr/src/main/java/com/example/xr/arcore/FaceTracking.kt +++ b/xr/src/main/java/com/example/xr/arcore/FaceTracking.kt @@ -20,10 +20,10 @@ import android.annotation.SuppressLint import androidx.xr.arcore.Face import androidx.xr.arcore.FaceBlendShapeType import androidx.xr.arcore.FaceConfidenceRegion +import androidx.xr.arcore.TrackingState import androidx.xr.runtime.FaceTrackingMode import androidx.xr.runtime.Session import androidx.xr.runtime.SessionConfigureSuccess -import androidx.xr.runtime.TrackingState // TODO(dbridie): Remove in next release. This was fixed in aosp/508d564ca343bfc190ecaddc07209e8a9c4bdaab. @SuppressLint("RestrictedApi") @@ -43,7 +43,7 @@ private fun configureFaceTracking(session: Session) { @Suppress("UnusedVariable") private suspend fun getUserFace(session: Session) { // [START androidxr_arcore_faceTracking_getFace] - val face = Face.getUserFace(session) ?: return + val face = Face.getUserFace(session) face.state.collect { state -> if (state.trackingState != TrackingState.TRACKING) return@collect diff --git a/xr/src/main/java/com/example/xr/arcore/Geospatial.kt b/xr/src/main/java/com/example/xr/arcore/Geospatial.kt index 80f32c78a..843d9786f 100644 --- a/xr/src/main/java/com/example/xr/arcore/Geospatial.kt +++ b/xr/src/main/java/com/example/xr/arcore/Geospatial.kt @@ -17,10 +17,9 @@ package com.example.xr.arcore import androidx.xr.arcore.ArDevice -import androidx.xr.arcore.CreateGeospatialPoseFromPoseErrorInternal import androidx.xr.arcore.CreateGeospatialPoseFromPoseNotTracking import androidx.xr.arcore.CreateGeospatialPoseFromPoseSuccess -import androidx.xr.arcore.CreatePoseFromGeospatialPoseErrorInternal +import androidx.xr.arcore.CreatePoseFromGeospatialPoseInternalError import androidx.xr.arcore.CreatePoseFromGeospatialPoseNotTracking import androidx.xr.arcore.CreatePoseFromGeospatialPoseSuccess import androidx.xr.arcore.Geospatial @@ -42,10 +41,10 @@ private fun configureGeospatialSession(session: Session) { // [START androidxr_arcore_geospatial_configure] // Define the configuration object to enable Geospatial features. val newConfig = Config( - // Set the GeospatialMode to VPS_AND_GPS. - geospatial = GeospatialMode.VPS_AND_GPS, - // Set the DeviceTrackingMode to SPATIAL_LAST_KNOWN. - deviceTracking = DeviceTrackingMode.SPATIAL_LAST_KNOWN + // Set the GeospatialMode to SPATIAL. + geospatial = GeospatialMode.SPATIAL, + // Set the DeviceTrackingMode to SPATIAL. + deviceTracking = DeviceTrackingMode.SPATIAL ) // Apply the configuration to the session. try { @@ -97,6 +96,9 @@ private suspend fun checkVpsAvailability(geospatial: Geospatial) { is VpsAvailabilityUnavailable -> { // VPS is not available at this location. } + else -> { + // A newer exception was added, but your app is using an old version of the library + } } // [END androidxr_arcore_geospatial_check_vps] } @@ -120,7 +122,9 @@ private fun convertDeviceToGeospatial(session: Session, geospatial: Geospatial) // Geospatial is not currently tracking. } - is CreateGeospatialPoseFromPoseErrorInternal -> TODO() + else -> { + // A newer exception was added, but your app is using an old version of the library + } } // [END androidxr_arcore_geospatial_device_to_geospatial] } @@ -136,9 +140,12 @@ private fun convertGeospatialToDevice(geospatial: Geospatial, geoPose: Geospatia is CreatePoseFromGeospatialPoseNotTracking -> { // Geospatial is not currently tracking. } - is CreatePoseFromGeospatialPoseErrorInternal -> { + is CreatePoseFromGeospatialPoseInternalError -> { // An internal error occurred. } + else -> { + // A newer exception was added, but your app is using an old version of the library + } } // [END androidxr_arcore_geospatial_pose_to_device] } diff --git a/xr/src/main/java/com/example/xr/arcore/Hands.kt b/xr/src/main/java/com/example/xr/arcore/Hands.kt index 0a62cc200..d55c4f272 100644 --- a/xr/src/main/java/com/example/xr/arcore/Hands.kt +++ b/xr/src/main/java/com/example/xr/arcore/Hands.kt @@ -50,7 +50,7 @@ fun ComponentActivity.configureSession(session: Session) { fun ComponentActivity.collectHands(session: Session) { lifecycleScope.launch { // [START androidxr_arcore_hand_collect] - Hand.left(session)?.state?.collect { handState -> // or Hand.right(session) + Hand.left(session).state.collect { handState -> // or Hand.right(session) // Hand state has been updated. // Use the state of hand joints to update an entity's position. renderPlanetAtHandPalm(handState) @@ -58,7 +58,7 @@ fun ComponentActivity.collectHands(session: Session) { // [END androidxr_arcore_hand_collect] } lifecycleScope.launch { - Hand.right(session)?.state?.collect { rightHandState -> + Hand.right(session).state.collect { rightHandState -> renderPlanetAtFingerTip(rightHandState) } } @@ -69,7 +69,7 @@ fun secondaryHandDetection(activity: Activity, session: Session) { // [START androidxr_arcore_hand_handedness] val handedness = Hand.getPrimaryHandSide(activity.contentResolver) val secondaryHand = if (handedness == HandSide.LEFT) Hand.right(session) else Hand.left(session) - val handState = secondaryHand?.state ?: return + val handState = secondaryHand.state detectGesture(handState) // [END androidxr_arcore_hand_handedness] } diff --git a/xr/src/main/java/com/example/xr/arcore/Mobile.kt b/xr/src/main/java/com/example/xr/arcore/Mobile.kt index e705ddd60..ed19e6e76 100644 --- a/xr/src/main/java/com/example/xr/arcore/Mobile.kt +++ b/xr/src/main/java/com/example/xr/arcore/Mobile.kt @@ -24,7 +24,7 @@ import androidx.xr.runtime.Session fun accessMobileRuntime(session: Session) { // [START androidxr_arcore_mobile_runtime] val arCoreRuntime = session.runtimes.firstNotNullOfOrNull { it as? ArCoreRuntime } ?: return - val originalSession = arCoreRuntime.lifecycleManager.session() + val originalSession = arCoreRuntime.session() val originalFrame = arCoreRuntime.perceptionManager.lastFrame() // [END androidxr_arcore_mobile_runtime] } diff --git a/xr/src/main/java/com/example/xr/compose/Orbiter.kt b/xr/src/main/java/com/example/xr/compose/Orbiter.kt index 81ff85459..516e0cd81 100644 --- a/xr/src/main/java/com/example/xr/compose/Orbiter.kt +++ b/xr/src/main/java/com/example/xr/compose/Orbiter.kt @@ -49,6 +49,7 @@ import androidx.xr.compose.subspace.layout.SubspaceModifier import androidx.xr.compose.subspace.layout.height import androidx.xr.compose.subspace.layout.movable import androidx.xr.compose.subspace.layout.resizable +import androidx.xr.compose.subspace.layout.transformingMovable import androidx.xr.compose.subspace.layout.width import androidx.xr.compose.unit.DpVolumeOffset import com.example.xr.R @@ -62,7 +63,7 @@ private fun OrbiterExampleSubspace() { .height(824.dp) .width(1400.dp) .resizable() - .movable(), + .transformingMovable(), ) { SpatialPanelContent() OrbiterExample() diff --git a/xr/src/main/java/com/example/xr/compose/SpatialGltfModel.kt b/xr/src/main/java/com/example/xr/compose/SpatialGltfModel.kt index 5498397f7..7f2dc09ba 100644 --- a/xr/src/main/java/com/example/xr/compose/SpatialGltfModel.kt +++ b/xr/src/main/java/com/example/xr/compose/SpatialGltfModel.kt @@ -16,7 +16,9 @@ package com.example.xr.compose +import android.util.Log import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -40,6 +42,7 @@ import kotlin.io.path.Path fun SpatialGltfModelExample(){ val xrSession = checkNotNull(LocalSession.current) val degrees = 1f + val removeMaterial = false // [START androidxr_compose_SpatialGltfModelState] val modelState = rememberSpatialGltfModelState( @@ -49,64 +52,101 @@ fun SpatialGltfModelExample(){ ) // [END androidxr_compose_SpatialGltfModelState] - // [START androidxr_compose_SpatialGltfModelMaterial] + // [START androidxr_compose_SpatialGltfModelNode] // Retrieve the list of nodes (individual components/meshes) defined within the glTF model. val entityNodes = modelState.nodes // Find a specific node by name to apply modifications, such as material overrides. val node = entityNodes.find { it.name == "node_name" } + // [END androidxr_compose_SpatialGltfModelNode] + + // [START androidxr_compose_SpatialGltfModelIntrospection] + LaunchedEffect(node, degrees) { + val rotation = Quaternion.fromEulerAngles(degrees, 0f, degrees) + node?.let { + it.localPose = Pose(it.localPose.translation, rotation) + } + } + // [END androidxr_compose_SpatialGltfModelIntrospection] + // [START androidxr_compose_SpatialGltfModelMaterial] // Maintain a reference to the custom material to avoid re-creating it on every recomposition. var pbrMaterial by remember { mutableStateOf(null) } // Create and apply the custom material once the session is ready and the target node is available. LaunchedEffect(node) { - val material = pbrMaterial ?: KhronosPbrMaterial.create( + val material = KhronosPbrMaterial.create( session = xrSession, alphaMode = AlphaMode.OPAQUE ).also { pbrMaterial = it + // Apply a base color factor (RGBA) to change the color of the model. + it.setBaseColorFactor( + Vector4( + x = 0.5f, + y = 0.0f, + z = 0.5f, + w = 1.0f + ) + ) + } + // [END androidxr_compose_SpatialGltfModelMaterial] + + // [START androidxr_compose_SpatialGltfModelMaterialOverride] + node?.setMaterialOverride( + material = material + ) + // [END androidxr_compose_SpatialGltfModelMaterialOverride] + + // [START androidxr_compose_SpatialGltfModelMaterialClear] + if (removeMaterial) { + node?.clearMaterialOverride() + } + // [END androidxr_compose_SpatialGltfModelMaterialClear] + } + + // [START androidxr_compose_SpatialGltfModelTexture] + LaunchedEffect(node) { + val material = KhronosPbrMaterial.create( + session = xrSession, + alphaMode = AlphaMode.OPAQUE + ).also { + pbrMaterial = it + // Load a texture val texture = Texture.create( session = xrSession, path = Path("textures/texture_name.png") ) - // Configure occlusion to define how the material surface handles ambient lighting. + // Set the texture and configure occlusion to define how the material surface handles ambient lighting. it.setOcclusionTexture( texture = texture, strength = 1.0f ) - - // Apply a base color factor (RGBA) to tint the model. - it.setBaseColorFactor( - Vector4( - x = 0.5f, - y = 0.0f, - z = 0.5f, - w = 1.0f - ) - ) } - - // Apply the custom PBR material to the specific node, overriding its original glTF material. node?.setMaterialOverride( material = material ) } - // [END androidxr_compose_SpatialGltfModelMaterial] + // [END androidxr_compose_SpatialGltfModelTexture] - // [START androidxr_compose_SpatialGltfModelIntrospection] - val arrows = modelState.nodes.find { - it.name == "Arrows" + // [START androidxr_compose_SpatialGltfModelAnimation] + val animation = modelState.animations.find { it.name == "Walk" } + + animation?.animationState?.let { state -> + LaunchedEffect(state) { + Log.i("SpatialGltfModelAnimationSample", "Animation State: $state") + } } - LaunchedEffect(arrows, degrees) { - val rotation = Quaternion.fromEulerAngles(degrees, 0f, degrees) - arrows?.localPose = - Pose(arrows.localPose.translation, rotation) + DisposableEffect(animation) { + animation?.loop() + onDispose { + animation?.stop() + } } - // [END androidxr_compose_SpatialGltfModelIntrospection] + // [END androidxr_compose_SpatialGltfModelAnimation] // [START androidxr_compose_SpatialGltfModelLoad] SpatialGltfModel(state = modelState, modifier = SubspaceModifier) diff --git a/xr/src/main/java/com/example/xr/compose/SpatialPanel.kt b/xr/src/main/java/com/example/xr/compose/SpatialPanel.kt index 72ead5780..ccfd7c756 100644 --- a/xr/src/main/java/com/example/xr/compose/SpatialPanel.kt +++ b/xr/src/main/java/com/example/xr/compose/SpatialPanel.kt @@ -34,6 +34,7 @@ import androidx.xr.compose.subspace.SpatialPanel import androidx.xr.compose.subspace.layout.SubspaceModifier import androidx.xr.compose.subspace.layout.height import androidx.xr.compose.subspace.layout.movable +import androidx.xr.compose.subspace.layout.transformingMovable import androidx.xr.compose.subspace.layout.width @Composable @@ -44,7 +45,7 @@ private fun SpatialPanelExample() { SubspaceModifier .height(824.dp) .width(1400.dp) - .movable(), + .transformingMovable(), resizePolicy = ResizePolicy(), ) { SpatialPanelContent() @@ -81,7 +82,7 @@ private fun ContentInSpatialPanel() { if (LocalSpatialCapabilities.current.isSpatialUiEnabled) { Subspace { SpatialPanel( - modifier = SubspaceModifier.movable(), + modifier = SubspaceModifier.transformingMovable(), resizePolicy = ResizePolicy(), ) { AppContent() diff --git a/xr/src/main/java/com/example/xr/glimmer/ButtonSamples.kt b/xr/src/main/java/com/example/xr/glimmer/ButtonSamples.kt new file mode 100644 index 000000000..3f5e5a461 --- /dev/null +++ b/xr/src/main/java/com/example/xr/glimmer/ButtonSamples.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.xr.glimmer + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.Send +import androidx.compose.material.icons.filled.Favorite +import androidx.compose.runtime.Composable +import androidx.xr.glimmer.Button +import androidx.xr.glimmer.GlimmerTheme +import androidx.xr.glimmer.Icon +import androidx.xr.glimmer.Text + +private val FavoriteIcon = Icons.Default.Favorite +private val SendIcon = Icons.AutoMirrored.Filled.Send + +// [START xr_glimmer_button] +@Composable +fun ButtonSample() { + Button( + onClick = { /* Handle navigation or action */ }, + leadingIcon = { Icon(FavoriteIcon, contentDescription = null) }, + trailingIcon = { Icon(SendIcon, contentDescription = null) } + ) { + Text("Text Label", style = GlimmerTheme.typography.titleSmall) + } +} +// [END xr_glimmer_button] diff --git a/xr/src/main/java/com/example/xr/glimmer/CardSamples.kt b/xr/src/main/java/com/example/xr/glimmer/CardSamples.kt new file mode 100644 index 000000000..f85212064 --- /dev/null +++ b/xr/src/main/java/com/example/xr/glimmer/CardSamples.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.xr.glimmer + +import androidx.compose.foundation.Image +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Favorite +import androidx.compose.runtime.Composable +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.xr.glimmer.Button +import androidx.xr.glimmer.Card +import androidx.xr.glimmer.GlimmerTheme +import androidx.xr.glimmer.Icon +import androidx.xr.glimmer.Text + +private val FavoriteIcon = Icons.Default.Favorite + +// [START xr_glimmer_card] +@Composable +fun CardSample() { + val myHeaderImage = painterResource(id = android.R.drawable.ic_menu_gallery) + Card( + title = { Text("Card Title", style = GlimmerTheme.typography.titleMedium) }, + subtitle = { Text("Sub-heading text", style = GlimmerTheme.typography.titleSmall) }, + leadingIcon = { Icon(FavoriteIcon, contentDescription = "Favorite") }, + header = { + Image( + painter = myHeaderImage, + contentDescription = "Header image", + contentScale = ContentScale.FillWidth + ) + }, + action = { + Button(onClick = { /* Handle action */ }) { + Text("Action") + } + } + ) { + Text( + "This is the main body content of the card, utilizing theme-tokens for consistent styling.", + style = GlimmerTheme.typography.bodyMedium + ) + } +} +// [END xr_glimmer_card] diff --git a/xr/src/main/java/com/example/xr/glimmer/DepthEffectLevelSamples.kt b/xr/src/main/java/com/example/xr/glimmer/DepthEffectLevelSamples.kt new file mode 100644 index 000000000..65833ae98 --- /dev/null +++ b/xr/src/main/java/com/example/xr/glimmer/DepthEffectLevelSamples.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.xr.glimmer + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.xr.glimmer.DepthEffect +import androidx.xr.glimmer.GlimmerTheme +import androidx.xr.glimmer.SurfaceDepthEffect +import androidx.xr.glimmer.Text +import androidx.xr.glimmer.surface + +// [START xr_glimmer_depth_effect_levels] +@Composable +fun DepthEffectLevelsSample() { + val levels = GlimmerTheme.depthEffectLevels + Box(contentAlignment = Alignment.Center) { + DepthBox("Level 1", levels.level1) + DepthBox("Level 2", levels.level2) + DepthBox("Level 3", levels.level3) + DepthBox("Level 4", levels.level4) + DepthBox("Level 5", levels.level5) + } +} + +@Composable +private fun DepthBox(name: String, depthEffect: DepthEffect) { + val interactionSource = remember { MutableInteractionSource() } + Box( + modifier = Modifier + .size(100.dp) + .surface( + interactionSource = interactionSource, + depthEffect = SurfaceDepthEffect( + depthEffect = depthEffect, + focusedDepthEffect = depthEffect + ) + ), + contentAlignment = Alignment.Center + ) { + Text(name) + } +} +// [END xr_glimmer_depth_effect_levels] diff --git a/xr/src/main/java/com/example/xr/glimmer/FocusSamples.kt b/xr/src/main/java/com/example/xr/glimmer/FocusSamples.kt new file mode 100644 index 000000000..8d3938ea7 --- /dev/null +++ b/xr/src/main/java/com/example/xr/glimmer/FocusSamples.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.xr.glimmer + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.compose.foundation.focusGroup +import androidx.compose.foundation.layout.Box +import androidx.compose.runtime.Composable +import androidx.compose.ui.ComposeUiFlags +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusProperties +import androidx.xr.glimmer.Button +import androidx.xr.glimmer.Text + +// [START xr_glimmer_focus_activity] +class GlassesActivityExample : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + @OptIn(ExperimentalComposeUiApi::class) + ComposeUiFlags.isInitialFocusOnFocusableAvailable = true + super.onCreate(savedInstanceState) + } +} +// [END xr_glimmer_focus_activity] + + +// [START xr_glimmer_focus_group] +@Composable +fun FocusSample(initialFocus: FocusRequester) { + Box( + modifier = + Modifier.focusProperties { + onEnter = { + initialFocus.requestFocus() + cancelFocusChange() + } + } + .focusGroup() + ) { + Button(onClick = {}) { Text("First Button") } + } +} +// [END xr_glimmer_focus_group] diff --git a/xr/src/main/java/com/example/xr/glimmer/GlimmerLazyColumnSamples.kt b/xr/src/main/java/com/example/xr/glimmer/GlimmerLazyColumnSamples.kt new file mode 100644 index 000000000..34c06fe76 --- /dev/null +++ b/xr/src/main/java/com/example/xr/glimmer/GlimmerLazyColumnSamples.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.xr.glimmer + +import androidx.compose.runtime.Composable +import androidx.xr.glimmer.ListItem +import androidx.xr.glimmer.Text +import androidx.xr.glimmer.TitleChip +import androidx.xr.glimmer.list.GlimmerLazyColumn +import androidx.xr.glimmer.list.items + +// [START xr_glimmer_lazy_column] +@Composable +fun GlimmerLazyColumnSample() { + GlimmerLazyColumn { + item { ListItem { Text("Header") } } + items(count = 10) { index -> ListItem { Text("Item-$index") } } + item { ListItem { Text("Footer") } } + } +} + +@Composable +fun GlimmerLazyColumnWithTitleChipSample() { + val ingredientItems = + listOf("Milk", "Flour", "Egg", "Salt", "Apples", "Butter", "Vanilla", "Sugar", "Cinnamon") + GlimmerLazyColumn(title = { TitleChip { Text("Ingredients") } }) { + items(ingredientItems) { text -> ListItem { Text(text) } } + } +} +// [END xr_glimmer_lazy_column] diff --git a/xr/src/main/java/com/example/xr/glimmer/IconButtonSamples.kt b/xr/src/main/java/com/example/xr/glimmer/IconButtonSamples.kt new file mode 100644 index 000000000..6fe1e7890 --- /dev/null +++ b/xr/src/main/java/com/example/xr/glimmer/IconButtonSamples.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.xr.glimmer + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Favorite +import androidx.compose.runtime.Composable +import androidx.xr.glimmer.Icon +import androidx.xr.glimmer.IconButton + +private val FavoriteIcon = Icons.Default.Favorite + +// [START xr_glimmer_icon_button] +@Composable +fun IconButtonSample() { + IconButton(onClick = { /* Handle action */ }) { + Icon(FavoriteIcon, contentDescription = "Localized description") + } +} +// [END xr_glimmer_icon_button] diff --git a/xr/src/main/java/com/example/xr/glimmer/IconSamples.kt b/xr/src/main/java/com/example/xr/glimmer/IconSamples.kt new file mode 100644 index 000000000..e0bcf0326 --- /dev/null +++ b/xr/src/main/java/com/example/xr/glimmer/IconSamples.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.xr.glimmer + +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Favorite +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.xr.glimmer.GlimmerTheme +import androidx.xr.glimmer.Icon + +private val FavoriteIcon = Icons.Default.Favorite + +// [START xr_glimmer_icon] +@Composable +fun IconSample() { + Icon(FavoriteIcon, contentDescription = "Localized description") +} +// [END xr_glimmer_icon] + + +// [START xr_glimmer_icon_colored] +@Composable +fun ColoredIconSample() { + Icon( + FavoriteIcon, + tint = GlimmerTheme.colors.primary, + contentDescription = "Localized description", + ) +} +// [END xr_glimmer_icon_colored] + + +// [START xr_glimmer_icon_sized] +@Composable +fun SizedIconSample() { + Icon( + FavoriteIcon, + contentDescription = "Localized description", + modifier = Modifier.size(GlimmerTheme.iconSizes.large), + ) +} +// [END xr_glimmer_icon_sized] diff --git a/xr/src/main/java/com/example/xr/glimmer/IconToggleButtonSamples.kt b/xr/src/main/java/com/example/xr/glimmer/IconToggleButtonSamples.kt new file mode 100644 index 000000000..e3ce1c217 --- /dev/null +++ b/xr/src/main/java/com/example/xr/glimmer/IconToggleButtonSamples.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.xr.glimmer + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Favorite +import androidx.compose.material.icons.outlined.FavoriteBorder +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.xr.glimmer.Icon +import androidx.xr.glimmer.IconToggleButton + +private val FavoriteIcon = Icons.Default.Favorite +private val OutlinedFavoriteIcon = Icons.Outlined.FavoriteBorder + +// [START xr_glimmer_icon_toggle_button] +@Composable +fun IconToggleButtonSample() { + var checked by remember { mutableStateOf(false) } + + IconToggleButton( + checked = checked, + onCheckedChange = { checked = it } + ) { + Icon( + if (checked) FavoriteIcon else OutlinedFavoriteIcon, + contentDescription = "Toggle favorite" + ) + } +} +// [END xr_glimmer_icon_toggle_button] diff --git a/xr/src/main/java/com/example/xr/glimmer/IndirectPointerGestureSamples.kt b/xr/src/main/java/com/example/xr/glimmer/IndirectPointerGestureSamples.kt new file mode 100644 index 000000000..4ce2bbe3c --- /dev/null +++ b/xr/src/main/java/com/example/xr/glimmer/IndirectPointerGestureSamples.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.xr.glimmer + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.focusTarget +import androidx.xr.glimmer.Text +import androidx.xr.glimmer.onIndirectPointerGesture + +// [START xr_glimmer_on_indirect_pointer_gesture] +@Composable +fun OnIndirectPointerGestureSample() { + var lastGesture by remember { mutableStateOf("None") } + + Box( + modifier = + Modifier.fillMaxSize() + .onIndirectPointerGesture( + enabled = true, + onSwipeForward = { lastGesture = "Forward" }, + onSwipeBackward = { lastGesture = "Backward" }, + onClick = { lastGesture = "Click" }, + ) + .focusTarget(), + contentAlignment = Alignment.Center, + ) { + Text("Last Gesture: $lastGesture") + } +} +// [END xr_glimmer_on_indirect_pointer_gesture] diff --git a/xr/src/main/java/com/example/xr/glimmer/ListItemSamples.kt b/xr/src/main/java/com/example/xr/glimmer/ListItemSamples.kt new file mode 100644 index 000000000..c2c910722 --- /dev/null +++ b/xr/src/main/java/com/example/xr/glimmer/ListItemSamples.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.xr.glimmer + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Favorite +import androidx.compose.runtime.Composable +import androidx.xr.glimmer.Icon +import androidx.xr.glimmer.ListItem +import androidx.xr.glimmer.Text + +private val FavoriteIcon = Icons.Default.Favorite + +// [START xr_glimmer_list_item] +@Composable +fun ListItemSample() { + ListItem { Text("This is a list item") } +} +// [END xr_glimmer_list_item] + + +// [START xr_glimmer_list_item_with_icons] +@Composable +fun ListItemWithIconsSample() { + ListItem( + leadingIcon = { Icon(FavoriteIcon, contentDescription = "Favorite") }, + trailingIcon = { Icon(FavoriteIcon, contentDescription = "Favorite") } + ) { + Text("This is a list item with icons") + } +} +// [END xr_glimmer_list_item_with_icons] diff --git a/xr/src/main/java/com/example/xr/glimmer/ListSamples.kt b/xr/src/main/java/com/example/xr/glimmer/ListSamples.kt new file mode 100644 index 000000000..d7df76418 --- /dev/null +++ b/xr/src/main/java/com/example/xr/glimmer/ListSamples.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.xr.glimmer + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.runtime.Composable +import androidx.compose.ui.unit.dp +import androidx.xr.glimmer.ListItem +import androidx.xr.glimmer.Text +import androidx.xr.glimmer.TitleChip +import androidx.xr.glimmer.list.GlimmerLazyColumn + +// [START xr_glimmer_list] +@Composable +fun VerticalListSample() { + GlimmerLazyColumn( + title = { TitleChip { Text("List Section Header") } }, + contentPadding = PaddingValues(16.dp), + verticalArrangement = Arrangement.spacedBy(20.dp) + ) { + item { + ListItem { + Text("Static Header Item") + } + } + + items(count = 5) { index -> + ListItem { + Text("Dynamic Item #$index") + } + } + + item { + ListItem { + Text("Static Footer Item") + } + } + } +} +// [END xr_glimmer_list] diff --git a/xr/src/main/java/com/example/xr/glimmer/ShapesSamples.kt b/xr/src/main/java/com/example/xr/glimmer/ShapesSamples.kt new file mode 100644 index 000000000..6b29de527 --- /dev/null +++ b/xr/src/main/java/com/example/xr/glimmer/ShapesSamples.kt @@ -0,0 +1,81 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.xr.glimmer + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.unit.dp +import androidx.xr.glimmer.GlimmerTheme +import androidx.xr.glimmer.Text +import androidx.xr.glimmer.list.GlimmerLazyColumn +import androidx.xr.glimmer.surface + +// [START xr_glimmer_shapes] +@Composable +fun ShapesSample() { + val shapes = GlimmerTheme.shapes + GlimmerLazyColumn { + item { ShapeItem("small", shape = shapes.small) } + item { ShapeItem("medium", shape = shapes.medium) } + item { ShapeItem("large", shape = shapes.large) } + } +} + +@Composable +private fun ShapeItem(name: String, shape: Shape, modifier: Modifier = Modifier) { + val interactionSource = remember { MutableInteractionSource() } + Box( + modifier + .aspectRatio(2.5f) + .fillMaxWidth() + .surface(interactionSource = interactionSource, shape = shape), + contentAlignment = Alignment.Center, + ) { + Text(name) + } +} +// [END xr_glimmer_shapes] + + +// [START xr_glimmer_custom_shapes] +@Composable +fun CustomShapesSample() { + val customShapes = GlimmerTheme.shapes.copy( + small = RoundedCornerShape(12.dp), + medium = RoundedCornerShape(20.dp) + ) + + val interactionSource = remember { MutableInteractionSource() } + Box( + Modifier + .aspectRatio(2.5f) + .fillMaxWidth() + .surface(interactionSource = interactionSource, shape = customShapes.small), + contentAlignment = Alignment.Center, + ) { + Text("custom small") + } +} +// [END xr_glimmer_custom_shapes] diff --git a/xr/src/main/java/com/example/xr/glimmer/StackSamples.kt b/xr/src/main/java/com/example/xr/glimmer/StackSamples.kt new file mode 100644 index 000000000..a479851db --- /dev/null +++ b/xr/src/main/java/com/example/xr/glimmer/StackSamples.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.xr.glimmer + +import androidx.compose.runtime.Composable +import androidx.xr.glimmer.Card +import androidx.xr.glimmer.Text +import androidx.xr.glimmer.stack.VerticalStack + +// [START xr_glimmer_stack] +@Composable +fun VerticalStackSample() { + VerticalStack { + item { + Card { Text("Top Card") } + } + item { + Card { Text("Second Card") } + } + item { + Card { Text("Third Card") } + } + } +} +// [END xr_glimmer_stack] diff --git a/xr/src/main/java/com/example/xr/glimmer/SurfaceSamples.kt b/xr/src/main/java/com/example/xr/glimmer/SurfaceSamples.kt new file mode 100644 index 000000000..975367504 --- /dev/null +++ b/xr/src/main/java/com/example/xr/glimmer/SurfaceSamples.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.xr.glimmer + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.xr.glimmer.Text +import androidx.xr.glimmer.surface + +// [START xr_glimmer_surface_interaction] +@Composable +fun FocusableSurfaceSample() { + val interactionSource = remember { MutableInteractionSource() } + Box( + modifier = Modifier + .size(100.dp) + .surface(interactionSource = interactionSource) + .focusable(interactionSource = interactionSource), + contentAlignment = Alignment.Center + ) { + Text("Focusable") + } +} + +@Composable +fun ClickableSurfaceSample() { + val interactionSource = remember { MutableInteractionSource() } + Box( + modifier = Modifier + .size(100.dp) + .surface(interactionSource = interactionSource) + .focusable(interactionSource = interactionSource) + .clickable( + interactionSource = interactionSource, + indication = null + ) { /* Handle click action */ }, + contentAlignment = Alignment.Center + ) { + Text("Clickable") + } +} +// [END xr_glimmer_surface_interaction] diff --git a/xr/src/main/java/com/example/xr/glimmer/TextSample.kt b/xr/src/main/java/com/example/xr/glimmer/TextSample.kt new file mode 100644 index 000000000..4d4499941 --- /dev/null +++ b/xr/src/main/java/com/example/xr/glimmer/TextSample.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.xr.glimmer + +import androidx.compose.runtime.Composable +import androidx.xr.glimmer.GlimmerTheme +import androidx.xr.glimmer.Text + +// [START xr_glimmer_text] +@Composable +fun TextSample() { + Text( + text = "Hello Glimmer", + style = GlimmerTheme.typography.titleLarge + ) +} +// [END xr_glimmer_text] diff --git a/xr/src/main/java/com/example/xr/glimmer/TitleChipSample.kt b/xr/src/main/java/com/example/xr/glimmer/TitleChipSample.kt new file mode 100644 index 000000000..31de864e1 --- /dev/null +++ b/xr/src/main/java/com/example/xr/glimmer/TitleChipSample.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.xr.glimmer + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Favorite +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.xr.glimmer.Card +import androidx.xr.glimmer.Icon +import androidx.xr.glimmer.Text +import androidx.xr.glimmer.TitleChip +import androidx.xr.glimmer.TitleChipDefaults + +private val FavoriteIcon = Icons.Default.Favorite + +// [START xr_glimmer_title_chip] +@Composable +fun BasicTitleChipSample() { + TitleChip { Text("Messages") } +} +// [END xr_glimmer_title_chip] + + +// [START xr_glimmer_title_chip_with_icon] +@Composable +fun TitleChipWithIconSample() { + TitleChip { + Icon(FavoriteIcon, contentDescription = "Favorite") + Text("Favorites") + } +} +// [END xr_glimmer_title_chip_with_icon] + + +// [START xr_glimmer_title_chip_with_card] +@Composable +fun TitleChipWithCardSample() { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + TitleChip { Text("Title Chip") } + + Spacer(Modifier.height(TitleChipDefaults.associatedContentSpacing)) + + Card( + title = { Text("Title") }, + subtitle = { Text("Subtitle") }, + leadingIcon = { Icon(FavoriteIcon, "Localized description") }, + ) { + Text("Card Content") + } + } +} +// [END xr_glimmer_title_chip_with_card] diff --git a/xr/src/main/java/com/example/xr/glimmer/ToggleButtonSamples.kt b/xr/src/main/java/com/example/xr/glimmer/ToggleButtonSamples.kt new file mode 100644 index 000000000..2350637c7 --- /dev/null +++ b/xr/src/main/java/com/example/xr/glimmer/ToggleButtonSamples.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.xr.glimmer + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Favorite +import androidx.compose.material.icons.outlined.FavoriteBorder +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.xr.glimmer.Icon +import androidx.xr.glimmer.Text +import androidx.xr.glimmer.ToggleButton + +private val FavoriteIcon = Icons.Default.Favorite +private val OutlinedFavoriteIcon = Icons.Outlined.FavoriteBorder + +// [START xr_glimmer_toggle_button] +@Composable +fun ToggleButtonSample() { + var checked by remember { mutableStateOf(false) } + val text = if (checked) "Toggle on" else "Toggle off" + + ToggleButton( + checked = checked, + onCheckedChange = { checked = it } + ) { + Text(text) + } +} +// [END xr_glimmer_toggle_button] + + +// [START xr_glimmer_toggle_button_with_icon] +@Composable +fun ToggleButtonWithLeadingIconSample() { + var checked by remember { mutableStateOf(false) } + + ToggleButton( + checked = checked, + leadingIcon = { + Icon( + if (checked) FavoriteIcon else OutlinedFavoriteIcon, + contentDescription = "Toggle favorite" + ) + }, + onCheckedChange = { checked = it } + ) { + Text(if (checked) "On" else "Off") + } +} +// [END xr_glimmer_toggle_button_with_icon] diff --git a/xr/src/main/java/com/example/xr/projected/GlassesMainActivity.kt b/xr/src/main/java/com/example/xr/projected/GlassesMainActivity.kt index 3be8f835f..fecbf023b 100644 --- a/xr/src/main/java/com/example/xr/projected/GlassesMainActivity.kt +++ b/xr/src/main/java/com/example/xr/projected/GlassesMainActivity.kt @@ -22,6 +22,7 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.result.ActivityResultLauncher +import androidx.compose.foundation.focusable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable @@ -151,7 +152,8 @@ fun HomeScreen( ) { Box( modifier = modifier - .surface(focusable = false) + .surface() + .focusable(false) .fillMaxSize(), contentAlignment = Alignment.Center ) { diff --git a/xr/src/main/java/com/example/xr/scenecore/Environments.kt b/xr/src/main/java/com/example/xr/scenecore/Environments.kt index 5ed80d36f..6e34d4341 100644 --- a/xr/src/main/java/com/example/xr/scenecore/Environments.kt +++ b/xr/src/main/java/com/example/xr/scenecore/Environments.kt @@ -18,8 +18,8 @@ package com.example.xr.scenecore import android.content.Context import androidx.xr.runtime.Session -import androidx.xr.scenecore.ExrImage import androidx.xr.scenecore.GltfModel +import androidx.xr.scenecore.ImageBasedLightingAsset import androidx.xr.scenecore.SpatialEnvironment import androidx.xr.scenecore.scene import java.nio.file.Paths @@ -33,11 +33,11 @@ private class Environments(val session: Session) { suspend fun loadEnvironmentSkybox() { // [START androidxr_scenecore_environment_loadEnvironmentSkybox] - val lightingForSkybox = ExrImage.createFromZip(session, Paths.get("BlueSkyboxLighting.zip")) + val lightingForSkybox = ImageBasedLightingAsset.createFromZip(session, Paths.get("BlueSkyboxLighting.zip")) // [END androidxr_scenecore_environment_loadEnvironmentSkybox] } - fun setEnvironmentPreference(environmentGeometry: GltfModel, lightingForSkybox: ExrImage) { + fun setEnvironmentPreference(environmentGeometry: GltfModel, lightingForSkybox: ImageBasedLightingAsset) { // [START androidxr_scenecore_environment_setEnvironmentPreference] val spatialEnvironmentPreference = SpatialEnvironment.SpatialEnvironmentPreference(lightingForSkybox, environmentGeometry) diff --git a/xr/src/main/java/com/example/xr/scenecore/SpatialAudio.kt b/xr/src/main/java/com/example/xr/scenecore/SpatialAudio.kt index 5b85c4d34..1f49d139d 100644 --- a/xr/src/main/java/com/example/xr/scenecore/SpatialAudio.kt +++ b/xr/src/main/java/com/example/xr/scenecore/SpatialAudio.kt @@ -158,7 +158,13 @@ private fun playSpatialAudioAtEntityAmbionics(session: Session, appContext: Cont @OptIn(UnstableApi::class) private fun detectSupport(context: Context) { // [START androidxr_scenecore_dolby_detect_support] - val audioCapabilities = AudioCapabilities.getCapabilities(context, androidx.media3.common.AudioAttributes.DEFAULT, null, emptyList()) + val audioCapabilities = AudioCapabilities.getCapabilities( + context, + androidx.media3.common.AudioAttributes.DEFAULT, + null, + emptyList() + ) + if (audioCapabilities.supportsEncoding(C.ENCODING_AC3)) { // Device supports playback of the Dolby Digital media format. }