SubspaceModifier
interface SubspaceModifier
SubspaceModifier.Companion |
The companion object |
SubspaceModifierNodeElement |
Modifier elements manage an instance of a particular |
An ordered, immutable collection of subspace modifier elements that decorate or add behavior to Subspace Compose elements.
Based on androidx.compose.ui.Modifier
Summary
Nested types |
|---|
|
The companion object |
abstract class SubspaceModifier.Node : DelegatableSubspaceNodeThe longer-lived object that is created for each |
Public companion functions |
|
|---|---|
infix SubspaceModifier |
|
open String |
toString() |
Public functions |
|
|---|---|
open Boolean |
all(Returns |
open Boolean |
any(Returns |
open R |
<R : Any?> foldIn(Accumulates a value starting with |
open R |
<R : Any?> foldOut(Accumulates a value starting with |
open infix SubspaceModifier |
then(other: SubspaceModifier)Concatenates this modifier with another. |
Extension functions |
|
|---|---|
SubspaceModifier |
SubspaceModifier.alpha(alpha: @FloatRange(from = 0.0, to = 1.0) Float)Sets the opacity of this element (and its children) to a value between 0..1. |
SubspaceModifier |
SubspaceModifier.aspectRatio(Attempts to size the content to match a specified aspect ratio by trying to match one of the incoming constraints in the following order: |
SubspaceModifier |
A |
SubspaceModifier |
SubspaceModifier.layout(Creates a node that allows changing how the wrapped element is measured and laid out. |
SubspaceModifier |
SubspaceModifier.movable(enabled: Boolean, movePolicy: MovePolicy)Configures this subspace element to be interactive and movable. |
SubspaceModifier |
SubspaceModifier.This function is deprecated. This signature is deprecated. |
SubspaceModifier |
SubspaceModifier.This function is deprecated. This signature is deprecated. |
SubspaceModifier |
SubspaceModifier.absoluteOffset(x: Dp, y: Dp, z: Dp)Offset the content by ( |
SubspaceModifier |
SubspaceModifier.offset(x: Dp, y: Dp, z: Dp) |
SubspaceModifier |
SubspaceModifier.onGloballyPositioned(Invoke |
SubspaceModifier |
SubspaceModifier.onSizeChanged(Invokes |
SubspaceModifier |
SubspaceModifier.absolutePadding(Apply additional space along each edge of the content in |
SubspaceModifier |
SubspaceModifier.padding(all: Dp)Apply |
SubspaceModifier |
SubspaceModifier.padding(horizontal: Dp, vertical: Dp, depth: Dp)Apply |
SubspaceModifier |
Apply additional space along each edge of the content in |
SubspaceModifier |
When present, this modifier defines how the pointer icon will be displayed when the spatial pointer hovers over an element. |
SubspaceModifier |
SubspaceModifier.resizable(When the resizable modifier is present and enabled, UI controls will be shown that allow the user to resize the element in 3D space. |
SubspaceModifier |
SubspaceModifier.This function is deprecated. Use the resizable modifier with ResizePolicy instead. |
SubspaceModifier |
This function is deprecated. SubspaceModifier.rotate() with no arguments does nothing. |
SubspaceModifier |
SubspaceModifier.rotate(quaternion: Quaternion)Rotate a subspace element (i.e. Panel) in space in regard to the center of the element. |
SubspaceModifier |
SubspaceModifier.rotate(axisAngle: Vector3, rotation: Float)Rotate a subspace element (i.e. Panel) in space in regard to the center of the element. |
SubspaceModifier |
SubspaceModifier.rotate(pitch: Float, yaw: Float, roll: Float)Rotate a subspace element (i.e. Panel) in space in regard to the center of the element. |
SubspaceModifier |
A |
SubspaceModifier |
SubspaceModifier.scale(Scale the contents of the composable by the scale factor along horizontal, vertical, and depth axes. |
SubspaceModifier |
SubspaceModifier.semantics(properties: SubspaceSemanticsPropertyReceiver.() -> Unit)Add semantics key/value pairs to the layout node, for use in testing, accessibility, etc. |
SubspaceModifier |
SubspaceModifier.depth(depth: Dp)Declare the preferred size of the content to be exactly |
SubspaceModifier |
SubspaceModifier.depthIn(min: Dp, max: Dp)Constrain the depth of the content to be between |
SubspaceModifier |
SubspaceModifier.fillMaxDepth(Have the content fill (possibly only partially) the |
SubspaceModifier |
SubspaceModifier.fillMaxHeight(Have the content fill (possibly only partially) the |
SubspaceModifier |
SubspaceModifier.fillMaxSize(Have the content fill (possibly only partially) the |
SubspaceModifier |
SubspaceModifier.fillMaxWidth(Have the content fill (possibly only partially) the |
SubspaceModifier |
SubspaceModifier.height(height: Dp)Declare the preferred size of the content to be exactly |
SubspaceModifier |
SubspaceModifier.heightIn(min: Dp, max: Dp)Constrain the height of the content to be between |
SubspaceModifier |
SubspaceModifier.requiredDepth(depth: Dp)Declare the size of the content to be exactly |
SubspaceModifier |
SubspaceModifier.requiredDepthIn(min: Dp, max: Dp)Constrain the depth of the content to be between |
SubspaceModifier |
SubspaceModifier.requiredHeight(height: Dp)Declare the size of the content to be exactly |
SubspaceModifier |
SubspaceModifier.requiredHeightIn(min: Dp, max: Dp)Constrain the height of the content to be between |
SubspaceModifier |
SubspaceModifier.requiredSize(size: Dp)Declare the size of the content to be exactly a |
SubspaceModifier |
Declare the size of the content to be exactly |
SubspaceModifier |
SubspaceModifier.requiredSize(width: Dp, height: Dp, depth: Dp)Declare the size of the content to be exactly |
SubspaceModifier |
SubspaceModifier.requiredSizeIn(Constrain the size of the content to be between min and max dp, disregarding the incoming measurement |
SubspaceModifier |
SubspaceModifier.requiredWidth(width: Dp)Declare the size of the content to be exactly |
SubspaceModifier |
SubspaceModifier.requiredWidthIn(min: Dp, max: Dp)Constrain the width of the content to be between |
SubspaceModifier |
SubspaceModifier.size(size: Dp)Declare the preferred size of the content to be exactly a |
SubspaceModifier |
SubspaceModifier.size(size: DpVolumeSize)Declare the preferred size of the content to be exactly |
SubspaceModifier |
SubspaceModifier.size(width: Dp, height: Dp, depth: Dp)Declare the preferred size of the content to be exactly |
SubspaceModifier |
SubspaceModifier.sizeIn(Constrain the size of the content to be between min and max dp as permitted by the incoming measurement constraints. |
SubspaceModifier |
SubspaceModifier.width(width: Dp)Declare the preferred size of the content to be exactly |
SubspaceModifier |
SubspaceModifier.widthIn(min: Dp, max: Dp)Constrain the width of the content to be between |
SubspaceModifier |
SubspaceModifier.spatializedAudioOutput(Adds spatialized audio output to this Composable. |
SubspaceModifier |
SubspaceModifier.testTag(tag: String)Applies a tag to allow modified element to be found in tests. |
SubspaceModifier |
SubspaceModifier.This function is deprecated. This modifier is deprecated. |
Public companion functions
then
infix fun then(other: SubspaceModifierNodeElement<SubspaceModifier.Node>): SubspaceModifier
Public functions
all
open fun all(
predicate: (SubspaceModifierNodeElement<SubspaceModifier.Node>) -> Boolean
): Boolean
Returns true if predicate returns true for all SubspaceModifierNodeElements in this SubspaceModifier or if this SubspaceModifier contains no Elements.
| Parameters | |
|---|---|
predicate: (SubspaceModifierNodeElement<SubspaceModifier.Node>) -> Boolean |
condition to evaluate for each element. |
any
open fun any(
predicate: (SubspaceModifierNodeElement<SubspaceModifier.Node>) -> Boolean
): Boolean
Returns true if predicate returns true for any SubspaceModifierNodeElement in this SubspaceModifier.
| Parameters | |
|---|---|
predicate: (SubspaceModifierNodeElement<SubspaceModifier.Node>) -> Boolean |
condition to evaluate for each element. |
foldIn
open fun <R : Any?> foldIn(
initial: R,
operation: (SubspaceModifierNodeElement<SubspaceModifier.Node>, R) -> R
): R
Accumulates a value starting with initial and applying operation to the current value and each SubspaceModifierNodeElement from outside in.
| Parameters | |
|---|---|
initial: R |
initial value for the accumulation. |
operation: (SubspaceModifierNodeElement<SubspaceModifier.Node>, R) -> R |
function to apply to the current accumulated value and the next element. |
foldOut
open fun <R : Any?> foldOut(
initial: R,
operation: (SubspaceModifierNodeElement<SubspaceModifier.Node>, R) -> R
): R
Accumulates a value starting with initial and applying operation to the current value and each SubspaceModifierNodeElement from inside out.
| Parameters | |
|---|---|
initial: R |
initial value for the accumulation. |
operation: (SubspaceModifierNodeElement<SubspaceModifier.Node>, R) -> R |
function to apply to the next element and the current accumulated value. |
then
open infix fun then(other: SubspaceModifier): SubspaceModifier
Concatenates this modifier with another.
Returns a SubspaceModifier representing this modifier followed by other in sequence.
| Parameters | |
|---|---|
other: SubspaceModifier |
|
Extension functions
SubspaceModifier.alpha
fun SubspaceModifier.alpha(alpha: @FloatRange(from = 0.0, to = 1.0) Float): SubspaceModifier
Sets the opacity of this element (and its children) to a value between 0..1. An alpha value of 0.0f means fully transparent while a value of 1.0f is completely opaque. Elements with semi-transparent alpha values (> 0.0 but < 1.0f) will be rendered using alpha-blending.
| Parameters | |
|---|---|
alpha: @FloatRange(from = 0.0, to = 1.0) Float |
the opacity of this element (and its children). Must be a value between 0 and 1, inclusive. Values < 0 or > 1 will be clamped. |
SubspaceModifier.aspectRatio
fun SubspaceModifier.aspectRatio(
ratio: @FloatRange(from = 0.0, fromInclusive = false) Float,
matchHeightConstraintsFirst: Boolean = false
): SubspaceModifier
Attempts to size the content to match a specified aspect ratio by trying to match one of the incoming constraints in the following order: VolumeConstraints.maxWidth, VolumeConstraints.maxHeight, VolumeConstraints.minWidth, VolumeConstraints.minHeight if matchHeightConstraintsFirst is false (which is the default), or VolumeConstraints.maxHeight, VolumeConstraints.maxWidth, VolumeConstraints.minHeight, VolumeConstraints.minWidth if matchHeightConstraintsFirst is true. The size in the other dimension is determined by the aspect ratio. The combinations will be tried in this order until one non-empty is found to satisfy the constraints. If no valid size is obtained this way, it means that there is no non-empty size satisfying both the constraints and the aspect ratio, so the constraints will not be respected and the content will be sized such that the VolumeConstraints.maxWidth or VolumeConstraints.maxHeight is matched (depending on matchHeightConstraintsFirst). Note that this modifier constrains the ratio between the content's width and height only. The depth dimension is not affected or constrained by this aspect ratio modifier.
Example usage:
SpatialPanel(SubspaceModifier.width(100.dp).aspectRatio(16f / 9f)) {
Text(text = "Inner Composable Content")
}
| Parameters | |
|---|---|
ratio: @FloatRange(from = 0.0, fromInclusive = false) Float |
the desired width/height positive ratio |
matchHeightConstraintsFirst: Boolean = false |
if true, height constraints will be matched before width constraints and used to calculate the resulting size according to |
SubspaceModifier.gravityAligned
fun SubspaceModifier.gravityAligned(): SubspaceModifier
A SubspaceModifier that forces the content to remain upright, aligned with gravity by isolating the element's yaw by cancelling parent pitch and roll.
This modifier calculates the necessary counter-rotation to ignore the pitch and roll of its parent entity's total world transformation. It only affects the rotation and not the translation. This is useful for UI elements like labels or billboards that should always stay level, regardless of the orientation of the object they are attached to. It effectively isolates the yaw (rotation around the vertical Y-axis).
SubspaceModifier.layout
fun SubspaceModifier.layout(
measure: SubspaceMeasureScope.(SubspaceMeasurable, VolumeConstraints) -> SubspaceMeasureResult
): SubspaceModifier
Creates a node that allows changing how the wrapped element is measured and laid out.
| Parameters | |
|---|---|
measure: SubspaceMeasureScope.(SubspaceMeasurable, VolumeConstraints) -> SubspaceMeasureResult |
function that defines the measurement and placement logic. |
SubspaceModifier.movable
fun SubspaceModifier.movable(
enabled: Boolean = true,
movePolicy: MovePolicy = MovePolicy.default()
): SubspaceModifier
Configures this subspace element to be interactive and movable.
When this modifier is present and enabled, draggable UI controls will be shown that allow the user to move the element in 3D space. The specific behavior of this movement—such as whether the system automatically applies the transformation, leaves it to the developer, or anchors it to physical surfaces—is defined by the provided MovePolicy. Input events used for moving in this way are consumed.
There are some limitations that should be considered when using this modifier:
-
the draggable UI controls of nested composables using the
movablemodifier may conflict with each other. -
Attaching multiple
movablemodifiers with auto-applying policies (likeMovePolicy.default) to the same element will compound the movement distance, since each modifier independently applies the drag offset upon release.
| Parameters | |
|---|---|
enabled: Boolean = true |
true if this composable should be movable. Setting this to false will remove the interactable affordance associated with the content. Disabling the modifier after movement keeps the composable at its last dragged position. Removing the modifier entirely resets the composable to its original layout position. |
movePolicy: MovePolicy = MovePolicy.default() |
The @sample androidx.xr.compose.samples.BasicMovableSample @sample androidx.xr.compose.samples.CustomMovableSample |
SubspaceModifier.movable
fun SubspaceModifier.movable(
enabled: Boolean = true,
scaleWithDistance: Boolean = true,
onMove: (SpatialMoveEvent) -> Unit
): SubspaceModifier
Configures this subspace element to accept move events and report the calculated pose updates via a callback, without automatically applying a resulting transformation.
When the movable modifier is present and enabled, draggable UI controls will be shown that allow the user to move the element in 3D space. This modifier enables custom behavior for movement of the content. The system calculates the target Pose based on input, but does not automatically apply it to the associated layout. The developer is responsible for consuming the onMove event and applying the result. (e.g., by updating a state backed by SubspaceModifier.offset)
There are some limitations that should be considered when using this modifier: 1) the draggable UI controls of nested composables using the transformingMovable modifier and movable modifier may conflict with each other, 2) It cannot be used with the following composables androidx.xr.compose.subspace.SpatialExternalSurfaceHemisphere and androidx.xr.compose.subspace.SpatialExternalSurfaceSphere due to their similarity with the system environment and not having any layout size, 3) If this element has animations that affect its layout properties (e.g., offset), these animations should be stopped when a move gesture starts (detected via the onMove callback with SpatialMoveEventType.Start) to prevent rendering jitter, and can be resumed when the gesture ends (SpatialMoveEventType.End).
| Parameters | |
|---|---|
enabled: Boolean = true |
true if this composable should be movable. Setting this to false will remove the interactable affordance associated with the content. Disabling the modifier after movement keeps the composable at its last dragged position. Removing the modifier entirely resets the composable to its original layout position. |
scaleWithDistance: Boolean = true |
true if this composable should scale in size when moved in depth. When enabled, the subspace element will grow if pushed away from the user or shrink when pulled toward the user in order to maintain the interact-ability and legibility of the panel. Scaling with distance respects other transformations applied to this layout. |
onMove: (SpatialMoveEvent) -> Unit |
callback invoked continuously during the interaction that receives a |
| See also | |
|---|---|
transformingMovable |
for implementing system controlled movement. |
SubspaceModifier.movable
fun SubspaceModifier.movable(
enabled: Boolean = true,
stickyPose: Boolean = false,
scaleWithDistance: Boolean = true,
onMoveStart: ((SpatialMoveEvent) -> Unit)? = null,
onMoveEnd: ((SpatialMoveEvent) -> Unit)? = null,
onMove: ((SpatialMoveEvent) -> Boolean)? = null
): SubspaceModifier
When the movable modifier is present and enabled, draggable UI controls will be shown that allow the user to move the element in 3D space.
There are some limitations that should be considered when using this modifier: 1) the draggable UI controls of nested composables using the movable modifier may conflict with each other, 2) when attaching multiple movable modifiers that handle movement internally, the movement effect will be compounded.
import androidx.compose.material3.Text import androidx.xr.compose.subspace.SpatialPanel import androidx.xr.compose.subspace.layout.MovePolicy import androidx.xr.compose.subspace.layout.SubspaceModifier import androidx.xr.compose.subspace.layout.movable SpatialPanel( modifier = SubspaceModifier.movable(enabled = true, movePolicy = MovePolicy.default()) ) { Text("The user can move me around!") }
import androidx.compose.material3.Text import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.xr.compose.subspace.SpatialPanel import androidx.xr.compose.subspace.layout.MovePolicy import androidx.xr.compose.subspace.layout.SpatialMoveEvent import androidx.xr.compose.subspace.layout.SubspaceModifier import androidx.xr.compose.subspace.layout.movable import androidx.xr.compose.subspace.layout.offset import androidx.xr.compose.subspace.layout.rotate import androidx.xr.runtime.math.Quaternion var offsetX by remember { mutableStateOf(0.dp) } var offsetY by remember { mutableStateOf(0.dp) } var offsetZ by remember { mutableStateOf(0.dp) } var rotation by remember { mutableStateOf(Quaternion.Identity) } val density = LocalDensity.current val customMovement: (SpatialMoveEvent) -> Unit = { moveEvent -> offsetX = with(density) { moveEvent.pose.translation.x.toDp() } offsetY = with(density) { moveEvent.pose.translation.y.toDp() } offsetZ = with(density) { moveEvent.pose.translation.z.toDp() } rotation = moveEvent.pose.rotation } SpatialPanel( modifier = SubspaceModifier.movable( enabled = true, movePolicy = MovePolicy.custom(onMove = customMovement), ) .offset(x = offsetX, y = offsetY, z = offsetZ) .rotate(rotation) ) { Text("The user can move me around!") }
| Parameters | |
|---|---|
enabled: Boolean = true |
true if this composable should be movable. |
stickyPose: Boolean = false |
if enabled, the user specified position will be retained when the modifier is disabled or removed. |
scaleWithDistance: Boolean = true |
true if this composable should scale in size when moved in depth. When this scaleWithDistance is enabled, the subspace element moved will grow or shrink. It will also maintain any explicit scale that it had before movement. |
onMoveStart: ((SpatialMoveEvent) -> Unit)? = null |
a callback to process the start of a move event. This will only be called if |
onMoveEnd: ((SpatialMoveEvent) -> Unit)? = null |
a callback to process the end of a move event. This will only be called if |
onMove: ((SpatialMoveEvent) -> Boolean)? = null |
a callback to process the pose change during movement, with translation in pixels. This will only be called if |
| See also | |
|---|---|
SpatialMoveEvent |
. |
SubspaceModifier.absoluteOffset
fun SubspaceModifier.absoluteOffset(x: Dp = 0.dp, y: Dp = 0.dp, z: Dp = 0.dp): SubspaceModifier
Offset the content by (x dp, y dp, z dp) without considering layout direction. The offsets can be positive as well as non-positive.
This modifier will not consider layout direction when calculating the position of the content: a positive x offset will always move the content to the right. For a modifier that considers the layout direction when applying the offset, see offset.
| Parameters | |
|---|---|
x: Dp = 0.dp |
horizontal offset in |
y: Dp = 0.dp |
vertical offset in |
z: Dp = 0.dp |
depth offset in |
| See also | |
|---|---|
offset |
SubspaceModifier.offset
fun SubspaceModifier.offset(x: Dp = 0.dp, y: Dp = 0.dp, z: Dp = 0.dp): SubspaceModifier
Offset the content by (x dp, y dp, z dp). The offsets can be positive as well as non-positive.
This modifier will automatically adjust the horizontal offset according to the layout direction: when the layout direction is LTR, positive x offsets will move the content to the right and when the layout direction is RTL, positive x offsets will move the content to the left. For a modifier that offsets without considering layout direction, see absoluteOffset.
| Parameters | |
|---|---|
x: Dp = 0.dp |
horizontal offset in |
y: Dp = 0.dp |
vertical offset in |
z: Dp = 0.dp |
depth offset in |
| See also | |
|---|---|
absoluteOffset |
SubspaceModifier.onGloballyPositioned
fun SubspaceModifier.onGloballyPositioned(
onGloballyPositioned: (SubspaceLayoutCoordinates) -> Unit
): SubspaceModifier
Invoke onGloballyPositioned with the SubspaceLayoutCoordinates of the element when the global position or the orientation of the content may have changed. Note that it will be called after a composition when the coordinates are finalized.
This callback executes after composition once the final coordinates are determined. It will be invoked at least once when the SubspaceLayoutCoordinates become available and subsequently whenever the composable's transform (position, rotation, scale) is updated relative to the subspace's origin.
The callback information will be relative to its subspace. For instance, When a nested subspace is moved by the global subspace its Pose will reflect its position in the nested subspace. Its position value will not be updated despite moving locations in the Global Subspace.
| Parameters | |
|---|---|
onGloballyPositioned: (SubspaceLayoutCoordinates) -> Unit |
callback to be invoked with the updated |
SubspaceModifier.onSizeChanged
fun SubspaceModifier.onSizeChanged(
onSizeChanged: (size: IntVolumeSize) -> Unit
): SubspaceModifier
Invokes onSizeChanged with the IntVolumeSize of the element when its size changes.
This callback is executed after the measure pass when the SubspaceComposable's size is finalized. It will be invoked at least once when the size becomes available and subsequently whenever the size changes.
For observing changes to position or orientation in addition to size, use SubspaceModifier.onGloballyPositioned instead.
| Parameters | |
|---|---|
onSizeChanged: (size: IntVolumeSize) -> Unit |
The callback that is invoked when the size changes. The |
SubspaceModifier.absolutePadding
fun SubspaceModifier.absolutePadding(
left: Dp = 0.dp,
top: Dp = 0.dp,
right: Dp = 0.dp,
bottom: Dp = 0.dp,
front: Dp = 0.dp,
back: Dp = 0.dp
): SubspaceModifier
Apply additional space along each edge of the content in Dp: left, top, right, bottom, front and back. Padding is applied before content measurement and takes precedence; content may only be as large as the remaining space. To apply relative padding with layout direction, see padding.
Negative padding is not permitted — it will cause IllegalArgumentException.
| Parameters | |
|---|---|
left: Dp = 0.dp |
The amount of space at the left edge of the content. |
top: Dp = 0.dp |
The amount of space at the top edge of the content. |
right: Dp = 0.dp |
The amount of space at the right edge of the content. |
bottom: Dp = 0.dp |
The amount of space at the bottom edge of the content. |
front: Dp = 0.dp |
The amount of space at the front edge of the content. |
back: Dp = 0.dp |
The amount of space at the back edge of the content. |
| See also | |
|---|---|
padding |
SubspaceModifier.padding
fun SubspaceModifier.padding(all: Dp): SubspaceModifier
Apply all dp of additional space along each edge of the content, left, top, right, bottom, front, and back. Padding is applied before content measurement and takes precedence; content may only be as large as the remaining space.
Negative padding is not permitted — it will cause IllegalArgumentException. See padding
| Parameters | |
|---|---|
all: Dp |
The amount of space at each edge of the content. |
SubspaceModifier.padding
fun SubspaceModifier.padding(
horizontal: Dp = 0.dp,
vertical: Dp = 0.dp,
depth: Dp = 0.dp
): SubspaceModifier
Apply horizontal dp space along the left and right edges of the content, vertical dp space along the top and bottom edges, and depth dp space along front and back edged. Padding is applied before content measurement and takes precedence; content may only be as large as the remaining space.
Negative padding is not permitted — it will cause IllegalArgumentException. See padding
SubspaceModifier.padding
fun SubspaceModifier.padding(
start: Dp = 0.dp,
top: Dp = 0.dp,
end: Dp = 0.dp,
bottom: Dp = 0.dp,
front: Dp = 0.dp,
back: Dp = 0.dp
): SubspaceModifier
Apply additional space along each edge of the content in Dp: start, top, end, bottom, front and back. The start and end edges will be determined by the current LayoutDirection. Padding is applied before content measurement and takes precedence; content may only be as large as the remaining space. To not consider the layout direction when applying the padding, see absolutePadding.
Negative padding is not permitted — it will cause IllegalArgumentException.
| Parameters | |
|---|---|
start: Dp = 0.dp |
The amount of space at the start edge of the content. Start edge is left if the layout direction is LTR, or right for RTL. |
top: Dp = 0.dp |
The amount of space at the top edge of the content. |
end: Dp = 0.dp |
The amount of space at the end edge of the content. End edge is right if the layout direction is LTR, or left for RTL. |
bottom: Dp = 0.dp |
The amount of space at the bottom edge of the content. |
front: Dp = 0.dp |
The amount of space at the front edge of the content. |
back: Dp = 0.dp |
The amount of space at the back edge of the content. |
| See also | |
|---|---|
absolutePadding |
SubspaceModifier.pointerHoverIcon
fun SubspaceModifier.pointerHoverIcon(icon: SpatialPointerIcon): SubspaceModifier
When present, this modifier defines how the pointer icon will be displayed when the spatial pointer hovers over an element. Any child elements will inherit the pointer icon setting that this modifier defines for their parent element upon hovering.
| Parameters | |
|---|---|
icon: SpatialPointerIcon |
The |
| Returns | |
|---|---|
SubspaceModifier |
A |
SubspaceModifier.resizable
fun SubspaceModifier.resizable(
enabled: Boolean = true,
minimumSize: DpVolumeSize = DpVolumeSize.Zero,
maximumSize: DpVolumeSize = DpVolumeSize(Dp.Infinity, Dp.Infinity, Dp.Infinity),
maintainAspectRatio: Boolean = false,
resizePolicy: ResizePolicy = ResizePolicy.default()
): SubspaceModifier
When the resizable modifier is present and enabled, UI controls will be shown that allow the user to resize the element in 3D space.
import androidx.compose.material3.Text import androidx.compose.ui.unit.dp import androidx.xr.compose.subspace.SpatialPanel import androidx.xr.compose.subspace.layout.SubspaceModifier import androidx.xr.compose.subspace.layout.resizable import androidx.xr.compose.unit.DpVolumeSize SpatialPanel( modifier = SubspaceModifier.resizable( minimumSize = DpVolumeSize(100.dp, 100.dp, 0.dp), maximumSize = DpVolumeSize(800.dp, 800.dp, 0.dp), ) ) { Text("Basic Resizable") }
import androidx.compose.material3.Text import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.xr.compose.subspace.SpatialPanel import androidx.xr.compose.subspace.layout.ResizePolicy import androidx.xr.compose.subspace.layout.SpatialResizeEventType import androidx.xr.compose.subspace.layout.SubspaceModifier import androidx.xr.compose.subspace.layout.height import androidx.xr.compose.subspace.layout.resizable import androidx.xr.compose.subspace.layout.width val density = LocalDensity.current var panelWidth by remember { mutableStateOf(400.dp) } var panelHeight by remember { mutableStateOf(300.dp) } SpatialPanel( modifier = SubspaceModifier.width(panelWidth) .height(panelHeight) .resizable( resizePolicy = ResizePolicy.custom { event -> // The developer decides when and how to apply the new size. // In this example, we update our state when the resize interaction // ends. if (event.type == SpatialResizeEventType.End) { with(density) { panelWidth = event.size.width.toDp() panelHeight = event.size.height.toDp() } } } ) ) { Text("Resizable with size state.") }
| Parameters | |
|---|---|
enabled: Boolean = true |
Whether resizing is enabled for this object. If |
minimumSize: DpVolumeSize = DpVolumeSize.Zero |
The minimum allowable size for the object, represented by a |
maximumSize: DpVolumeSize = DpVolumeSize(Dp.Infinity, Dp.Infinity, Dp.Infinity) |
The maximum allowable size for the object, represented by a |
maintainAspectRatio: Boolean = false |
If |
resizePolicy: ResizePolicy = ResizePolicy.default() |
The policy that determines how the size change is applied. Defaults to ResizePolicy.default() which automatically handles resizing under the hood. |
SubspaceModifier.resizable
fun SubspaceModifier.resizable(
enabled: Boolean = true,
minimumSize: DpVolumeSize = DpVolumeSize.Zero,
maximumSize: DpVolumeSize = DpVolumeSize(Dp.Infinity, Dp.Infinity, Dp.Infinity),
maintainAspectRatio: Boolean = false,
onResizeStart: (IntVolumeSize) -> Unit = {},
onResizeUpdate: (IntVolumeSize) -> Unit = {},
onResizeEnd: (IntVolumeSize) -> Boolean = { false }
): SubspaceModifier
When the resizable modifier is present and enabled, UI controls will be shown that allow the user to resize the element in 3D space.
| Parameters | |
|---|---|
enabled: Boolean = true |
Whether resizing is enabled for this object. If |
minimumSize: DpVolumeSize = DpVolumeSize.Zero |
The minimum allowable size for the object, represented by a |
maximumSize: DpVolumeSize = DpVolumeSize(Dp.Infinity, Dp.Infinity, Dp.Infinity) |
The maximum allowable size for the object, represented by a |
maintainAspectRatio: Boolean = false |
If |
onResizeStart: (IntVolumeSize) -> Unit = {} |
A callback to be called when the resize event starts. |
onResizeUpdate: (IntVolumeSize) -> Unit = {} |
A callback to be called when the size changes during a resize event. |
onResizeEnd: (IntVolumeSize) -> Boolean = { false } |
A callback to be called when the object's size changes, after a resize event has ended. It receives an |
SubspaceModifier.rotate
fun SubspaceModifier.rotate(): SubspaceModifier
This overload is provided to prevent rotate from being called with no arguments.
SubspaceModifier.rotate
fun SubspaceModifier.rotate(quaternion: Quaternion): SubspaceModifier
Rotate a subspace element (i.e. Panel) in space in regard to the center of the element. The rotation is directly specified by the provided Quaternion. The Quaternion values are specified as x,y,z,w. Where w is the rotation of the unit vector, in radians. Rotation does not alter a Composable's measured size in its parent.
| Parameters | |
|---|---|
quaternion: Quaternion |
Quaternion describing the rotation. |
SubspaceModifier.rotate
fun SubspaceModifier.rotate(axisAngle: Vector3, rotation: Float): SubspaceModifier
Rotate a subspace element (i.e. Panel) in space in regard to the center of the element. The rotation is defined by a Vector3 and a rotation angle in degrees. The axis angle will be normalized during construction. The rotation will be applied to the unit vector representing the axisAngle. Rotation does not alter a Composable's measured size in its parent.
SubspaceModifier.rotate
fun SubspaceModifier.rotate(pitch: Float = 0.0f, yaw: Float = 0.0f, roll: Float = 0.0f): SubspaceModifier
Rotate a subspace element (i.e. Panel) in space in regard to the center of the element. Parameter rotation angles are specified in degrees. The rotations are applied with the order pitch, then yaw, then roll. If no values are provided, no rotation is applied. Rotation does not alter a Composable's measured size in its parent.
| Parameters | |
|---|---|
pitch: Float = 0.0f |
Rotation around the x-axis. Defaults to |
yaw: Float = 0.0f |
Rotation around the y-axis. Defaults to |
roll: Float = 0.0f |
Rotation around the z-axis. Defaults to |
SubspaceModifier.rotateToLookAtUser
@ExperimentalRotateToLookAtUserApi
fun SubspaceModifier.rotateToLookAtUser(upDirection: Vector3 = Vector3.Up): SubspaceModifier
A SubspaceModifier that continuously rotates content so that it faces the user at all times.
A user of this API should configure the activity's Session object with DeviceTrackingMode.SPATIAL which requires android.permission.HEAD_TRACKING Android permission be granted by the calling application. session.configure( config = Config.Builder(session.config).setDeviceTracking(DeviceTrackingMode.SPATIAL).build() )
This modifier might not work as expected when used on content within a androidx.xr.compose.spatial.FollowingSubspace.
The preceding rotate modifiers will be disregarded because this modifier will override them. But the rotate after the rotateToLookAtUser modifier will be respected.
To achieve a "billboard" effect—where the content rotates to face the user on the Y-axis while remaining upright and aligned with gravity—combine this with gravityAligned.
import androidx.compose.material3.Text import androidx.xr.compose.spatial.Subspace import androidx.xr.compose.subspace.SpatialPanel import androidx.xr.compose.subspace.layout.SubspaceModifier import androidx.xr.compose.subspace.layout.gravityAligned import androidx.xr.compose.subspace.layout.rotate import androidx.xr.compose.subspace.layout.rotateToLookAtUser Subspace { SpatialPanel(modifier = SubspaceModifier.rotateToLookAtUser().gravityAligned()) { Text("I always face you and stay upright!") } }
import androidx.compose.material3.Text import androidx.xr.compose.spatial.Subspace import androidx.xr.compose.subspace.SpatialPanel import androidx.xr.compose.subspace.layout.SubspaceModifier import androidx.xr.compose.subspace.layout.rotate import androidx.xr.compose.subspace.layout.rotateToLookAtUser import androidx.xr.runtime.math.Vector3 Subspace { SpatialPanel( modifier = SubspaceModifier.rotateToLookAtUser( upDirection = Vector3(0f, 1f, 2f) ) // A slightly tilted "up" reference ) { Text("I have a custom 'Up' vector.") } }
import androidx.compose.material3.Text import androidx.xr.compose.spatial.Subspace import androidx.xr.compose.subspace.SpatialBox import androidx.xr.compose.subspace.SpatialPanel import androidx.xr.compose.subspace.layout.SubspaceModifier import androidx.xr.compose.subspace.layout.rotate import androidx.xr.compose.subspace.layout.rotateToLookAtUser import androidx.xr.runtime.math.Quaternion val parentRotation = Quaternion.fromEulerAngles(pitch = 40f, yaw = 30f, roll = 20f) Subspace { SpatialBox(SubspaceModifier.rotate(parentRotation)) { // This panel will rotate to face the user regardless of where // the parent SpatialBox is placed in the ActivitySpace. SpatialPanel(modifier = SubspaceModifier.rotateToLookAtUser()) { Text("I'm inside a SpatialBox, but I still see you!") } } }
| Parameters | |
|---|---|
upDirection: Vector3 = Vector3.Up |
Defines the reference "up" direction for the content's orientation. Pointing the content's forward vector at the user leaves the rotation around that axis (roll) undefined; this vector resolves that ambiguity. The default is Vector3.Up, which corresponds to the up direction of the ActivitySpace. |
SubspaceModifier.scale
fun SubspaceModifier.scale(
scale: @FloatRange(from = 0.0, fromInclusive = false) Float
): SubspaceModifier
Scale the contents of the composable by the scale factor along horizontal, vertical, and depth axes. Scaling does not change the measured size of the composable content during layout. Measured size of androidx.xr.compose.subspace.SubspaceComposable elements can be controlled using size Modifiers. Scale factor should be a positive number.
| Parameters | |
|---|---|
scale: @FloatRange(from = 0.0, fromInclusive = false) Float |
Multiplier to scale content along vertical, horizontal, depth axes. |
SubspaceModifier.semantics
fun SubspaceModifier.semantics(properties: SubspaceSemanticsPropertyReceiver.() -> Unit): SubspaceModifier
Add semantics key/value pairs to the layout node, for use in testing, accessibility, etc.
Mental Model (Picture Frame vs. Canvas): When building a combined UI in Compose for XR, think of a Subspace node (such as SpatialPanel) as a "Picture Frame" existing in 3D space, and the standard 2D Compose UI elements inside it as the "Canvas".
-
Use
SubspaceModifier.semanticson the 3D container (the frame) to provide spatial properties (such astestTagandcontentDescription) for 3D placement, anchoring, or testing. -
Use standard
androidx.compose.ui.semantics.semanticsmodifiers on the 2D Compose composables (the canvas) for fine-grained user interactions and TalkBack accessibility.
Interop & Merging Guidance: The 3D Subspace semantics tree and the 2D foundational semantics tree operate as distinct hierarchies. Spatial containers do not support merging descendant semantics (mergeDescendants = true).
import androidx.compose.foundation.layout.Column import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.xr.compose.spatial.Subspace import androidx.xr.compose.subspace.SpatialPanel import androidx.xr.compose.subspace.layout.SubspaceModifier import androidx.xr.compose.subspace.semantics.contentDescription import androidx.xr.compose.subspace.semantics.semantics import androidx.xr.compose.subspace.semantics.testTag @Composable fun AppContent() { // Mental Model: The "Picture Frame" (3D Container) vs. the "Canvas" (2D Content) Subspace { SpatialPanel( modifier = SubspaceModifier.semantics { // Set spatial semantics on the 3D container (the "Picture Frame") testTag = "main_settings_panel" contentDescription = "System Settings Window" } ) { // Standard 2D Compose UI goes inside (the "Canvas"). // These elements use standard Modifier.semantics for TalkBack and interactions. Column { Text("System Settings") Button( onClick = { /* do something */ }, modifier = Modifier.semantics { contentDescription = "Perform action" }, ) { Text("Click Me") } } } } }
| Parameters | |
|---|---|
properties: SubspaceSemanticsPropertyReceiver.() -> Unit |
Builder block where the semantics properties are defined. |
SubspaceModifier.depth
fun SubspaceModifier.depth(depth: Dp): SubspaceModifier
Declare the preferred size of the content to be exactly depth dp along the z dimension of the Composable's local coordinate space.
SubspaceModifier.depthIn
fun SubspaceModifier.depthIn(min: Dp = Dp.Unspecified, max: Dp = Dp.Unspecified): SubspaceModifier
Constrain the depth of the content to be between mindp and maxdp as permitted by the incoming measurement constraints. If the incoming constraints are more restrictive the requested size will obey the incoming constraints and attempt to be as close as possible to the preferred size.
SubspaceModifier.fillMaxDepth
fun SubspaceModifier.fillMaxDepth(
fraction: @FloatRange(from = 0.0, to = 1.0) Float = 1.0f
): SubspaceModifier
Have the content fill (possibly only partially) the VolumeConstraints.maxDepth of the incoming measurement constraints, by setting the minimum depth and the maximum depth to be equal to the maximum depth multiplied by fraction. Note that, by default, the fraction is 1, so the modifier will make the content fill the whole available depth. If the incoming maximum depth is VolumeConstraints.INFINITY this modifier will have no effect.
| Parameters | |
|---|---|
fraction: @FloatRange(from = 0.0, to = 1.0) Float = 1.0f |
The fraction of the maximum height to use, between |
SubspaceModifier.fillMaxHeight
fun SubspaceModifier.fillMaxHeight(
fraction: @FloatRange(from = 0.0, to = 1.0) Float = 1.0f
): SubspaceModifier
Have the content fill (possibly only partially) the VolumeConstraints.maxHeight of the incoming measurement constraints, by setting the minimum height and the maximum height to be equal to the maximum height multiplied by fraction. Note that, by default, the fraction is 1, so the modifier will make the content fill the whole available height. If the incoming maximum height is VolumeConstraints.INFINITY this modifier will have no effect.
| Parameters | |
|---|---|
fraction: @FloatRange(from = 0.0, to = 1.0) Float = 1.0f |
The fraction of the maximum height to use, between |
SubspaceModifier.fillMaxSize
fun SubspaceModifier.fillMaxSize(
fraction: @FloatRange(from = 0.0, to = 1.0) Float = 1.0f
): SubspaceModifier
Have the content fill (possibly only partially) the VolumeConstraints.maxWidth, VolumeConstraints.maxHeight, and VolumeConstraints.maxDepth of the incoming measurement constraints. See SubspaceModifier.fillMaxWidth, SubspaceModifier.fillMaxHeight, and SubspaceModifier.fillMaxDepth for details. Note that, by default, the fraction is 1, so the modifier will make the content fill the whole available space. If the incoming maximum width or height or depth is VolumeConstraints.INFINITY this modifier will have no effect in that dimension.
| Parameters | |
|---|---|
fraction: @FloatRange(from = 0.0, to = 1.0) Float = 1.0f |
The fraction of the maximum size to use, between |
SubspaceModifier.fillMaxWidth
fun SubspaceModifier.fillMaxWidth(
fraction: @FloatRange(from = 0.0, to = 1.0) Float = 1.0f
): SubspaceModifier
Have the content fill (possibly only partially) the VolumeConstraints.maxWidth of the incoming measurement constraints, by setting the minimum width and the maximum width to be equal to the maximum width multiplied by fraction. Note that, by default, the fraction is 1, so the modifier will make the content fill the whole available width. If the incoming maximum width is VolumeConstraints.INFINITY this modifier will have no effect.
| Parameters | |
|---|---|
fraction: @FloatRange(from = 0.0, to = 1.0) Float = 1.0f |
The fraction of the maximum width to use, between |
SubspaceModifier.height
fun SubspaceModifier.height(height: Dp): SubspaceModifier
Declare the preferred size of the content to be exactly height dp along the y dimension of the Composable's local coordinate space.
SubspaceModifier.heightIn
fun SubspaceModifier.heightIn(min: Dp = Dp.Unspecified, max: Dp = Dp.Unspecified): SubspaceModifier
Constrain the height of the content to be between mindp and maxdp as permitted by the incoming measurement constraints. If the incoming constraints are more restrictive the requested size will obey the incoming constraints and attempt to be as close as possible to the preferred size.
SubspaceModifier.requiredDepth
fun SubspaceModifier.requiredDepth(depth: Dp): SubspaceModifier
Declare the size of the content to be exactly depth dp along the z dimension of the Composable's local coordinate space, disregarding the incoming VolumeConstraints.
This is in contrast to SubspaceModifier.depth, which respects the parent's constraints. requiredDepth will ignore the minDepth and maxDepth from the incoming constraints, which can be useful for sizing an element to a specific value even if it exceeds the parent's bounds.
SubspaceModifier.requiredDepthIn
fun SubspaceModifier.requiredDepthIn(
min: Dp = Dp.Unspecified,
max: Dp = Dp.Unspecified
): SubspaceModifier
Constrain the depth of the content to be between min dp and max dp, disregarding the incoming measurement VolumeConstraints.
This is in contrast to SubspaceModifier.depthIn, which respects the parent's constraints. requiredDepthIn will ignore the minDepth and maxDepth from the incoming constraints.
SubspaceModifier.requiredHeight
fun SubspaceModifier.requiredHeight(height: Dp): SubspaceModifier
Declare the size of the content to be exactly height dp along the y dimension of the Composable's local coordinate space, disregarding the incoming VolumeConstraints.
This is in contrast to SubspaceModifier.height, which respects the parent's constraints. requiredHeight will ignore the minHeight and maxHeight from the incoming constraints, which can be useful for sizing an element to a specific value even if it exceeds the parent's bounds.
SubspaceModifier.requiredHeightIn
fun SubspaceModifier.requiredHeightIn(
min: Dp = Dp.Unspecified,
max: Dp = Dp.Unspecified
): SubspaceModifier
Constrain the height of the content to be between min dp and max dp, disregarding the incoming measurement VolumeConstraints.
This is in contrast to SubspaceModifier.heightIn, which respects the parent's constraints. requiredHeightIn will ignore the minHeight and maxHeight from the incoming constraints.
SubspaceModifier.requiredSize
fun SubspaceModifier.requiredSize(size: Dp): SubspaceModifier
Declare the size of the content to be exactly a size dp cube, disregarding the incoming VolumeConstraints. When applied to a Panel, the size will be a size dp square instead.
This is in contrast to SubspaceModifier.size, which respects the parent's constraints. requiredSize will ignore all min and max constraints from the incoming constraints, which can be useful for sizing an element to a specific value even if it exceeds the parent's bounds.
SubspaceModifier.requiredSize
fun SubspaceModifier.requiredSize(size: DpVolumeSize): SubspaceModifier
Declare the size of the content to be exactly size in each of the three dimensions of the Composable's local coordinate space, disregarding the incoming VolumeConstraints.
This is in contrast to SubspaceModifier.size, which respects the parent's constraints. requiredSize will ignore all min and max constraints from the incoming constraints, which can be useful for sizing an element to a specific value even if it exceeds the parent's bounds. The parent will then determine how to handle the overflow.
| Parameters | |
|---|---|
size: DpVolumeSize |
required volume size as a |
SubspaceModifier.requiredSize
fun SubspaceModifier.requiredSize(
width: Dp = Dp.Unspecified,
height: Dp = Dp.Unspecified,
depth: Dp = Dp.Unspecified
): SubspaceModifier
Declare the size of the content to be exactly width, height, and depth in each of the three dimensions of the Composable's local coordinate space, disregarding the incoming VolumeConstraints.
This is in contrast to SubspaceModifier.size, which respects the parent's constraints. requiredSize will ignore all min and max constraints from the incoming constraints, which can be useful for sizing an element to a specific value even if it exceeds the parent's bounds. The parent will then determine how to handle the overflow.
SubspaceModifier.requiredSizeIn
fun SubspaceModifier.requiredSizeIn(
minWidth: Dp = Dp.Unspecified,
maxWidth: Dp = Dp.Unspecified,
minHeight: Dp = Dp.Unspecified,
maxHeight: Dp = Dp.Unspecified,
minDepth: Dp = Dp.Unspecified,
maxDepth: Dp = Dp.Unspecified
): SubspaceModifier
Constrain the size of the content to be between min and max dp, disregarding the incoming measurement VolumeConstraints.
This is in contrast to SubspaceModifier.sizeIn, which respects the parent's constraints. requiredSizeIn will ignore the min and max constraints from the incoming constraints, which can be useful for sizing an element to a specific range even if it exceeds the parent's bounds.
| Parameters | |
|---|---|
minWidth: Dp = Dp.Unspecified |
The minimum width. |
maxWidth: Dp = Dp.Unspecified |
The maximum width. |
minHeight: Dp = Dp.Unspecified |
The minimum height. |
maxHeight: Dp = Dp.Unspecified |
The maximum height. |
minDepth: Dp = Dp.Unspecified |
The minimum depth. |
maxDepth: Dp = Dp.Unspecified |
The maximum depth. |
SubspaceModifier.requiredWidth
fun SubspaceModifier.requiredWidth(width: Dp): SubspaceModifier
Declare the size of the content to be exactly width dp along the x dimension of the Composable's local coordinate space, disregarding the incoming VolumeConstraints.
This is in contrast to SubspaceModifier.width, which respects the parent's constraints. requiredWidth will ignore the minWidth and maxWidth from the incoming constraints, which can be useful for sizing an element to a specific value even if it exceeds the parent's bounds.
SubspaceModifier.requiredWidthIn
fun SubspaceModifier.requiredWidthIn(
min: Dp = Dp.Unspecified,
max: Dp = Dp.Unspecified
): SubspaceModifier
Constrain the width of the content to be between min dp and max dp, disregarding the incoming measurement VolumeConstraints.
This is in contrast to SubspaceModifier.widthIn, which respects the parent's constraints. requiredWidthIn will ignore the minWidth and maxWidth from the incoming constraints.
SubspaceModifier.size
fun SubspaceModifier.size(size: Dp): SubspaceModifier
Declare the preferred size of the content to be exactly a size dp cube.
SubspaceModifier.size
fun SubspaceModifier.size(size: DpVolumeSize): SubspaceModifier
Declare the preferred size of the content to be exactly size in each of the three dimensions. Applying rotations to an object will also rotate the xyz axes of this modifier.
| Parameters | |
|---|---|
size: DpVolumeSize |
preferred volume size as a |
SubspaceModifier.size
fun SubspaceModifier.size(
width: Dp = Dp.Unspecified,
height: Dp = Dp.Unspecified,
depth: Dp = Dp.Unspecified
): SubspaceModifier
Declare the preferred size of the content to be exactly width dp along the x dimensions, height dp along the y dimensions, and depth dp along the z dimension, of the Composable's local coordinate space.
SubspaceModifier.sizeIn
fun SubspaceModifier.sizeIn(
minWidth: Dp = Dp.Unspecified,
maxWidth: Dp = Dp.Unspecified,
minHeight: Dp = Dp.Unspecified,
maxHeight: Dp = Dp.Unspecified,
minDepth: Dp = Dp.Unspecified,
maxDepth: Dp = Dp.Unspecified
): SubspaceModifier
Constrain the size of the content to be between min and max dp as permitted by the incoming measurement constraints. If the incoming constraints are more restrictive the requested size will obey the incoming constraints and attempt to be as close as possible to the preferred size.
| Parameters | |
|---|---|
minWidth: Dp = Dp.Unspecified |
The minimum width. |
maxWidth: Dp = Dp.Unspecified |
The maximum width. |
minHeight: Dp = Dp.Unspecified |
The minimum height. |
maxHeight: Dp = Dp.Unspecified |
The maximum height. |
minDepth: Dp = Dp.Unspecified |
The minimum depth. |
maxDepth: Dp = Dp.Unspecified |
The maximum depth. |
SubspaceModifier.width
fun SubspaceModifier.width(width: Dp): SubspaceModifier
Declare the preferred size of the content to be exactly width dp along the x dimension of the Composable's local coordinate space.
SubspaceModifier.widthIn
fun SubspaceModifier.widthIn(min: Dp = Dp.Unspecified, max: Dp = Dp.Unspecified): SubspaceModifier
Constrain the width of the content to be between mindp and maxdp as permitted by the incoming measurement constraints. If the incoming constraints are more restrictive the requested size will obey the incoming constraints and attempt to be as close as possible to the preferred size.
SubspaceModifier.spatializedAudioOutput
fun SubspaceModifier.spatializedAudioOutput(
spatializedAudioOutput: SpatializedAudioOutput
): SubspaceModifier
Adds spatialized audio output to this Composable. Use PointSourceExoplayerAudioOutput, SoundFieldExoplayerAudioOutput or SoundEffectPoolComponent.asSpatializedAudioOutput to create an object capable of spatializing audio.
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.media3.common.MediaItem import androidx.media3.exoplayer.ExoPlayer 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.width import androidx.xr.compose.subspace.media.rememberPointSourceExoplayerAudioOutput import androidx.xr.compose.subspace.media.spatializedAudioOutput import androidx.xr.scenecore.PointSourceParams val context = LocalContext.current // Create a PointSourceExoplayerAudioOutput. val audioOutput = rememberPointSourceExoplayerAudioOutput(PointSourceParams()) // Create an ExoPlayer and set its AudioOutputProvider. val exoPlayer = remember { ExoPlayer.Builder(context).setAudioOutputProvider(audioOutput.audioOutputProvider).build() } DisposableEffect(Unit) { exoPlayer.setMediaItem(MediaItem.fromUri("asset:///audio.mp3")) exoPlayer.prepare() exoPlayer.play() onDispose { exoPlayer.release() } } // Attach the SpatializedAudioOutput via SubspaceModifier.spatializedAudioOutput. The audio will // be spatialized from the position of this SpatialPanel, and will follow the Panel as it moves. SpatialPanel( modifier = SubspaceModifier.width(600.dp) .height(400.dp) .spatializedAudioOutput(audioOutput) .movable() ) { // Content of the panel }
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.media3.common.MediaItem import androidx.media3.exoplayer.ExoPlayer import androidx.xr.compose.platform.LocalSession 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.width import androidx.xr.compose.subspace.media.SoundFieldExoplayerAudioOutput import androidx.xr.compose.subspace.media.spatializedAudioOutput import androidx.xr.scenecore.SoundFieldAttributes import androidx.xr.scenecore.SpatializerConstants val context = LocalContext.current val session = LocalSession.current ?: return // Create a SoundFieldExoplayerAudioOutput. val audioOutput = remember { SoundFieldExoplayerAudioOutput( session, SoundFieldAttributes(SpatializerConstants.AmbisonicsOrder.FIRST_ORDER), ) } // Create an ExoPlayer and set its AudioOutputProvider. val exoPlayer = remember { ExoPlayer.Builder(context).setAudioOutputProvider(audioOutput.audioOutputProvider).build() } DisposableEffect(Unit) { exoPlayer.setMediaItem(MediaItem.fromUri("asset:///ambient.mp3")) exoPlayer.prepare() exoPlayer.play() onDispose { exoPlayer.release() } } // Attach the SpatializedAudioOutput via SubspaceModifier.spatializedAudioOutput. SpatialPanel( modifier = SubspaceModifier.width(600.dp) .height(400.dp) .spatializedAudioOutput(audioOutput) .movable() ) { // Content of the panel }
import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.xr.compose.platform.LocalSession 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.width import androidx.xr.compose.subspace.media.asSpatializedAudioOutput import androidx.xr.compose.subspace.media.spatializedAudioOutput import androidx.xr.scenecore.PointSourceParams import androidx.xr.scenecore.SoundEffectPool import androidx.xr.scenecore.SoundEffectPoolComponent val context = LocalContext.current val session = LocalSession.current ?: return // Create a SoundEffectPool. val soundEffectPool = remember { SoundEffectPool.create(session, maxStreams = 10) } // Remember a sound effect loaded via AssetFileDescriptor. val soundEffect = remember { val assetFileDescriptor = context.assets.openFd("click.wav") soundEffectPool.load(assetFileDescriptor) } // Create a SoundEffectPoolComponent. val soundEffectPoolComponent = remember(session, soundEffectPool) { SoundEffectPoolComponent.create(session, soundEffectPool, PointSourceParams()) } // Attach the SpatializedAudioOutput via SubspaceModifier.spatializedAudioOutput. SpatialPanel( modifier = SubspaceModifier.width(600.dp) .height(400.dp) .spatializedAudioOutput(soundEffectPoolComponent.asSpatializedAudioOutput()) .movable() ) { Button( onClick = { soundEffectPoolComponent.play( soundEffect, volume = 1f, priority = 0, isLooping = false, ) } ) { Text("Play Sound Effect") } }
| Parameters | |
|---|---|
spatializedAudioOutput: SpatializedAudioOutput |
A |
SubspaceModifier.testTag
fun SubspaceModifier.testTag(tag: String): SubspaceModifier
Applies a tag to allow modified element to be found in tests.
This is a convenience method for a semantics that sets SubspaceSemanticsPropertyReceiver.testTag.
| Parameters | |
|---|---|
tag: String |
String used to identify the modified element in tests. |
SubspaceModifier.transformingMovable
fun SubspaceModifier.transformingMovable(
enabled: Boolean = true,
scaleWithDistance: Boolean = true,
onMove: ((SpatialMoveEvent) -> Unit)? = null
): SubspaceModifier
Configures this subspace element to be interactive and movable, delegating the pose transformation to the system.
When this modifier is present and enabled, draggable UI controls will be shown that allow the user to move the element in 3D space. The system intercepts spatial input events, calculates the resulting Pose and scale, and automatically applies these transformations to the element's layout. This is the default behavior for standard movable UI elements where a 1:1 transformation is desired, and custom gesture handling or manual state management is not required. Input events used for moving in this way are consumed.
Modifier Chaining & Ordering Behavior
When combining transformingMovable with orientation modifiers such as rotateToLookAtUser, the order of the modifier chain significantly affects the translation and rotation behavior:
-
transformingMovable().rotateToLookAtUser(): SincetransformingMovableacts as the parent, the drag translation is applied directly inActivitySpacecoordinates. The childrotateToLookAtUsercontinuously overrides local rotation to point toward the user's head pose. To prevent visual conflicts and jitter with the system's drag gesture orientation handling, continuous head-tracking updates are intentionally suppressed while a system move is ongoing. Consequently, during active dragging, the panel uses the default smooth drag orientation, and active face-user head-tracking resumes once the drag gesture ends. -
rotateToLookAtUser().transformingMovable(): SincerotateToLookAtUseracts as the parent, the childtransformingMovabletranslates within the parent's rotated space. As a result, the translation offset applied by the drag is rotated by the parent's orientation, causing the final translation of the panel inActivitySpaceto be mathematically rotated relative to the straight drag path.
There are some limitations that should be considered when using this modifier: 1) the draggable UI controls of nested composables using the transformingMovable modifier and movable modifier may conflict with each other, 2) attaching multiple transformingMovable modifiers to the same element will compound the movement distance, since each modifier independently applies the drag offset upon release. 3) It should not be used with the following composables androidx.xr.compose.subspace.SpatialExternalSurfaceHemisphere and androidx.xr.compose.subspace.SpatialExternalSurfaceSphere due to their similarity with the system environment and not having any layout size. 4) If this element has animations that affect its layout properties (e.g., offset), these animations should be stopped when a move gesture starts (detected via the onMove callback with SpatialMoveEventType.Start) to prevent rendering jitter, and can be resumed when the gesture ends (SpatialMoveEventType.End).
import androidx.compose.material3.Text import androidx.xr.compose.spatial.Subspace import androidx.xr.compose.subspace.SpatialPanel import androidx.xr.compose.subspace.layout.SubspaceModifier import androidx.xr.compose.subspace.layout.transformingMovable SpatialPanel(modifier = SubspaceModifier.transformingMovable()) { Text("The user can move me around!") }
import androidx.compose.material3.Text import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.xr.compose.spatial.Subspace import androidx.xr.compose.subspace.SpatialPanel import androidx.xr.compose.subspace.layout.SpatialMoveEvent import androidx.xr.compose.subspace.layout.SubspaceModifier import androidx.xr.compose.subspace.layout.offset import androidx.xr.compose.subspace.layout.transformingMovable val density = LocalDensity.current var xOffset by remember { mutableStateOf(0.dp) } var yOffset by remember { mutableStateOf(0.dp) } var zOffset by remember { mutableStateOf(0.dp) } val customMovement: (SpatialMoveEvent) -> Unit = { moveEvent -> with(density) { xOffset = moveEvent.pose.translation.x.toDp() yOffset = moveEvent.pose.translation.y.toDp() zOffset = moveEvent.pose.translation.z.toDp() } } Subspace { SpatialPanel(modifier = SubspaceModifier.transformingMovable(onMove = customMovement)) { Text("The user can move me around") } SpatialPanel(modifier = SubspaceModifier.offset(x = xOffset, y = yOffset, z = zOffset)) { Text("Sibling Panel") } }
| Parameters | |
|---|---|
enabled: Boolean = true |
true if this composable should be movable. Setting this to false will remove the interactable affordance associated with the content. Disabling the modifier after movement keeps the composable at its last dragged position. Removing the modifier entirely resets the composable to its original layout position. |
scaleWithDistance: Boolean = true |
true if this composable should scale in size when moved in depth. When enabled, the subspace element will grow if pushed away from the user or shrink when pulled toward the user in order to maintain the interact-ability and legibility of the panel. Scaling with distance respects other transformations applied to this layout. |
onMove: ((SpatialMoveEvent) -> Unit)? = null |
Optional observer callback invoked during the manipulation. Since the system automatically applies the move, this callback is strictly for monitoring changes and should not control the position. The |
| See also | |
|---|---|
movable |
for implementing custom movement behaviors |