Loading packages/SystemUI/src/com/android/systemui/screencapture/ui/compose/RegionBox.kt +164 −9 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.systemui.screencapture.ui.compose import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.offset Loading @@ -25,26 +26,133 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.MaterialTheme 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.geometry.Offset import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.max import androidx.compose.ui.unit.min /** An enum to identify each of the four corners of the rectangle. */ enum class Corner { TopLeft, TopRight, BottomLeft, BottomRight, } /** * A static, non-interactive box with border lines and centered corner knobs. * A stateful composable that manages the size of a resizable RegionBox. * * @param width The width of the box. * @param height The height of the box. * @param initialWidth The initial width of the box. * @param initialHeight The initial height of the box. * @param onDragEnd A callback function that is invoked with the final width and height when the * user finishes a drag gesture. * @param modifier The modifier to be applied to the composable. */ @Composable fun RegionBox(width: Dp, height: Dp, modifier: Modifier = Modifier) { fun RegionBox( initialWidth: Dp, initialHeight: Dp, onDragEnd: (width: Dp, height: Dp) -> Unit, modifier: Modifier = Modifier, ) { // The minimum size allowed for the rectangle. // TODO(b/422565042): change when its value is finalized. val MIN_SIZE = 48.dp // The current state of the box. var width by remember { mutableStateOf(initialWidth) } var height by remember { mutableStateOf(initialHeight) } val density = LocalDensity.current val onDrag: (dragAmount: Offset, corner: Corner, maxWidth: Dp, maxHeight: Dp) -> Unit = { dragAmount, corner, maxWidth, maxHeight -> val (dragX, dragY) = dragAmount with(density) { // Calculate the potential new width and height based on the drag amount. // The width and height are increased or decreased by twice the drag amount as the // drag is calculated from the center of the box, so we need to add twice the drag // amount to account for both sides of the box. val newWidth = when (corner) { Corner.TopLeft, Corner.BottomLeft -> width - (dragX.toDp() * 2) Corner.TopRight, Corner.BottomRight -> width + (dragX.toDp() * 2) } val newHeight = when (corner) { Corner.TopLeft, Corner.TopRight -> height - (dragY.toDp() * 2) Corner.BottomLeft, Corner.BottomRight -> height + (dragY.toDp() * 2) } // The new width and height cannot be smaller than the minimum allowed, or bigger // than the screen size. width = max(min(newWidth, maxWidth), MIN_SIZE) height = max(min(newHeight, maxHeight), MIN_SIZE) } } ResizableRectangle( width = width, height = height, onDrag = onDrag, onDragEnd = { onDragEnd(width, height) }, modifier = modifier, ) } /** * A box with border lines and centered corner knobs that can be resized. * * @param width The current width of the box. * @param height The current height of the box. * @param onDrag Callback invoked when a knob is dragged. * @param onDragEnd Callback invoked when a drag gesture finishes. * @param modifier The modifier to be applied to the composable. */ @Composable private fun ResizableRectangle( width: Dp, height: Dp, onDrag: (dragAmount: Offset, corner: Corner, maxWidth: Dp, maxHeight: Dp) -> Unit, onDragEnd: () -> Unit, modifier: Modifier = Modifier, ) { // The diameter of the resizable knob on each corner of the region box. val KNOB_DIAMETER = 8.dp // The width of the border stroke around the region box. val BORDER_STROKE_WIDTH = 4.dp // Must remember the screen size for the drag logic. Initial values are set to 0. var screenWidth by remember { mutableStateOf(0.dp) } var screenHeight by remember { mutableStateOf(0.dp) } val density = LocalDensity.current // The box that contains the whole screen. Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Box( modifier = modifier .fillMaxSize() // .onSizeChanged gives us the final size of this box, which is the screen size, // after it has been drawn. .onSizeChanged { sizeInPixels -> screenWidth = with(density) { sizeInPixels.width.toDp() } screenHeight = with(density) { sizeInPixels.height.toDp() } }, contentAlignment = Alignment.Center, ) { // The box container for the region box and its knobs. Box(modifier = Modifier.size(width, height)) { // The main box for the region selection. Loading @@ -61,27 +169,73 @@ fun RegionBox(width: Dp, height: Dp, modifier: Modifier = Modifier) { Knob( diameter = KNOB_DIAMETER, modifier = Modifier.align(Alignment.TopStart).offset(x = -knobOffset, y = -knobOffset), Modifier.align(Alignment.TopStart) .offset(x = -knobOffset, y = -knobOffset) .pointerInput(Unit) { detectDragGestures( onDragEnd = onDragEnd, onDrag = { change, dragAmount -> change.consume() onDrag(dragAmount, Corner.TopLeft, screenWidth, screenHeight) }, ) }, ) // Top right knob Knob( diameter = KNOB_DIAMETER, modifier = Modifier.align(Alignment.TopEnd).offset(x = knobOffset, y = -knobOffset), modifier = Modifier.align(Alignment.TopEnd) .offset(x = knobOffset, y = -knobOffset) .pointerInput(Unit) { detectDragGestures( onDragEnd = onDragEnd, onDrag = { change, dragAmount -> change.consume() onDrag(dragAmount, Corner.TopRight, screenWidth, screenHeight) }, ) }, ) // Bottom left knob Knob( diameter = KNOB_DIAMETER, modifier = Modifier.align(Alignment.BottomStart).offset(x = -knobOffset, y = knobOffset), Modifier.align(Alignment.BottomStart) .offset(x = -knobOffset, y = knobOffset) .pointerInput(Unit) { detectDragGestures( onDragEnd = onDragEnd, onDrag = { change, dragAmount -> change.consume() onDrag(dragAmount, Corner.BottomLeft, screenWidth, screenHeight) }, ) }, ) // Bottom right knob Knob( diameter = KNOB_DIAMETER, modifier = Modifier.align(Alignment.BottomEnd).offset(x = knobOffset, y = knobOffset), Modifier.align(Alignment.BottomEnd) .offset(x = knobOffset, y = knobOffset) .pointerInput(Unit) { detectDragGestures( onDragEnd = onDragEnd, onDrag = { change, dragAmount -> change.consume() onDrag( dragAmount, Corner.BottomRight, screenWidth, screenHeight, ) }, ) }, ) } } Loading @@ -91,6 +245,7 @@ fun RegionBox(width: Dp, height: Dp, modifier: Modifier = Modifier) { * The circular knob on each corner of the box used for dragging each corner. * * @param diameter The diameter of the knob. * @param modifier The modifier to be applied to the composable. */ @Composable private fun Knob(diameter: Dp, modifier: Modifier = Modifier) { Loading Loading
packages/SystemUI/src/com/android/systemui/screencapture/ui/compose/RegionBox.kt +164 −9 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.systemui.screencapture.ui.compose import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.offset Loading @@ -25,26 +26,133 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.MaterialTheme 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.geometry.Offset import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.max import androidx.compose.ui.unit.min /** An enum to identify each of the four corners of the rectangle. */ enum class Corner { TopLeft, TopRight, BottomLeft, BottomRight, } /** * A static, non-interactive box with border lines and centered corner knobs. * A stateful composable that manages the size of a resizable RegionBox. * * @param width The width of the box. * @param height The height of the box. * @param initialWidth The initial width of the box. * @param initialHeight The initial height of the box. * @param onDragEnd A callback function that is invoked with the final width and height when the * user finishes a drag gesture. * @param modifier The modifier to be applied to the composable. */ @Composable fun RegionBox(width: Dp, height: Dp, modifier: Modifier = Modifier) { fun RegionBox( initialWidth: Dp, initialHeight: Dp, onDragEnd: (width: Dp, height: Dp) -> Unit, modifier: Modifier = Modifier, ) { // The minimum size allowed for the rectangle. // TODO(b/422565042): change when its value is finalized. val MIN_SIZE = 48.dp // The current state of the box. var width by remember { mutableStateOf(initialWidth) } var height by remember { mutableStateOf(initialHeight) } val density = LocalDensity.current val onDrag: (dragAmount: Offset, corner: Corner, maxWidth: Dp, maxHeight: Dp) -> Unit = { dragAmount, corner, maxWidth, maxHeight -> val (dragX, dragY) = dragAmount with(density) { // Calculate the potential new width and height based on the drag amount. // The width and height are increased or decreased by twice the drag amount as the // drag is calculated from the center of the box, so we need to add twice the drag // amount to account for both sides of the box. val newWidth = when (corner) { Corner.TopLeft, Corner.BottomLeft -> width - (dragX.toDp() * 2) Corner.TopRight, Corner.BottomRight -> width + (dragX.toDp() * 2) } val newHeight = when (corner) { Corner.TopLeft, Corner.TopRight -> height - (dragY.toDp() * 2) Corner.BottomLeft, Corner.BottomRight -> height + (dragY.toDp() * 2) } // The new width and height cannot be smaller than the minimum allowed, or bigger // than the screen size. width = max(min(newWidth, maxWidth), MIN_SIZE) height = max(min(newHeight, maxHeight), MIN_SIZE) } } ResizableRectangle( width = width, height = height, onDrag = onDrag, onDragEnd = { onDragEnd(width, height) }, modifier = modifier, ) } /** * A box with border lines and centered corner knobs that can be resized. * * @param width The current width of the box. * @param height The current height of the box. * @param onDrag Callback invoked when a knob is dragged. * @param onDragEnd Callback invoked when a drag gesture finishes. * @param modifier The modifier to be applied to the composable. */ @Composable private fun ResizableRectangle( width: Dp, height: Dp, onDrag: (dragAmount: Offset, corner: Corner, maxWidth: Dp, maxHeight: Dp) -> Unit, onDragEnd: () -> Unit, modifier: Modifier = Modifier, ) { // The diameter of the resizable knob on each corner of the region box. val KNOB_DIAMETER = 8.dp // The width of the border stroke around the region box. val BORDER_STROKE_WIDTH = 4.dp // Must remember the screen size for the drag logic. Initial values are set to 0. var screenWidth by remember { mutableStateOf(0.dp) } var screenHeight by remember { mutableStateOf(0.dp) } val density = LocalDensity.current // The box that contains the whole screen. Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Box( modifier = modifier .fillMaxSize() // .onSizeChanged gives us the final size of this box, which is the screen size, // after it has been drawn. .onSizeChanged { sizeInPixels -> screenWidth = with(density) { sizeInPixels.width.toDp() } screenHeight = with(density) { sizeInPixels.height.toDp() } }, contentAlignment = Alignment.Center, ) { // The box container for the region box and its knobs. Box(modifier = Modifier.size(width, height)) { // The main box for the region selection. Loading @@ -61,27 +169,73 @@ fun RegionBox(width: Dp, height: Dp, modifier: Modifier = Modifier) { Knob( diameter = KNOB_DIAMETER, modifier = Modifier.align(Alignment.TopStart).offset(x = -knobOffset, y = -knobOffset), Modifier.align(Alignment.TopStart) .offset(x = -knobOffset, y = -knobOffset) .pointerInput(Unit) { detectDragGestures( onDragEnd = onDragEnd, onDrag = { change, dragAmount -> change.consume() onDrag(dragAmount, Corner.TopLeft, screenWidth, screenHeight) }, ) }, ) // Top right knob Knob( diameter = KNOB_DIAMETER, modifier = Modifier.align(Alignment.TopEnd).offset(x = knobOffset, y = -knobOffset), modifier = Modifier.align(Alignment.TopEnd) .offset(x = knobOffset, y = -knobOffset) .pointerInput(Unit) { detectDragGestures( onDragEnd = onDragEnd, onDrag = { change, dragAmount -> change.consume() onDrag(dragAmount, Corner.TopRight, screenWidth, screenHeight) }, ) }, ) // Bottom left knob Knob( diameter = KNOB_DIAMETER, modifier = Modifier.align(Alignment.BottomStart).offset(x = -knobOffset, y = knobOffset), Modifier.align(Alignment.BottomStart) .offset(x = -knobOffset, y = knobOffset) .pointerInput(Unit) { detectDragGestures( onDragEnd = onDragEnd, onDrag = { change, dragAmount -> change.consume() onDrag(dragAmount, Corner.BottomLeft, screenWidth, screenHeight) }, ) }, ) // Bottom right knob Knob( diameter = KNOB_DIAMETER, modifier = Modifier.align(Alignment.BottomEnd).offset(x = knobOffset, y = knobOffset), Modifier.align(Alignment.BottomEnd) .offset(x = knobOffset, y = knobOffset) .pointerInput(Unit) { detectDragGestures( onDragEnd = onDragEnd, onDrag = { change, dragAmount -> change.consume() onDrag( dragAmount, Corner.BottomRight, screenWidth, screenHeight, ) }, ) }, ) } } Loading @@ -91,6 +245,7 @@ fun RegionBox(width: Dp, height: Dp, modifier: Modifier = Modifier) { * The circular knob on each corner of the box used for dragging each corner. * * @param diameter The diameter of the knob. * @param modifier The modifier to be applied to the composable. */ @Composable private fun Knob(diameter: Dp, modifier: Modifier = Modifier) { Loading