Loading packages/SystemUI/src/com/android/systemui/screencapture/record/largescreen/ui/compose/PreCaptureUI.kt +2 −7 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.systemui.screencapture.record.largescreen.ui.compose import android.graphics.Rect import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth Loading @@ -24,9 +25,7 @@ import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import com.android.systemui.res.R Loading Loading @@ -74,11 +73,7 @@ fun PreCaptureUI(viewModel: PreCaptureViewModel) { // TODO(b/427541309) Set the initial width and height of the RegionBox based on the // viewmodel state. RegionBox( initialWidth = 100.dp, initialHeight = 100.dp, onDragEnd = { _: Offset, _: Dp, _: Dp -> // TODO(b/427541309) Update the region box in the viewmodel. }, onRegionSelected = { rect: Rect -> viewModel.updateRegionBox(rect) }, drawableLoaderViewModel = viewModel, ) } Loading packages/SystemUI/src/com/android/systemui/screencapture/record/largescreen/ui/compose/RegionBox.kt +197 −135 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.systemui.screencapture.record.largescreen.ui.compose import android.graphics.Rect as IntRect import androidx.compose.foundation.border import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.layout.Box Loading @@ -31,18 +32,29 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import com.android.systemui.res.R import com.android.systemui.screencapture.common.ui.compose.PrimaryButton import com.android.systemui.screencapture.common.ui.compose.loadIcon import com.android.systemui.screencapture.common.ui.viewmodel.DrawableLoaderViewModel import kotlin.math.max import kotlin.math.min import kotlin.math.roundToInt // The different modes of interaction that the user can have with the RegionBox. private enum class DragMode { DRAWING, MOVING, RESIZING, NONE, } /** * Determines which zone (corner or edge) of a box is being touched based on the press offset. Loading @@ -59,6 +71,18 @@ private fun getTouchedZone( startOffset: Offset, touchAreaPx: Float, ): ResizeZone? { // Check if the touch is within the touch area of the box. val touchedZone = Rect( left = -touchAreaPx, top = -touchAreaPx, right = boxWidth + touchAreaPx, bottom = boxHeight + touchAreaPx, ) if (!touchedZone.contains(startOffset)) { return null } val isTouchingTop = startOffset.y in -touchAreaPx..touchAreaPx val isTouchingBottom = startOffset.y in (boxHeight - touchAreaPx)..(boxHeight + touchAreaPx) val isTouchingLeft = startOffset.x in -touchAreaPx..touchAreaPx Loading @@ -82,107 +106,146 @@ private fun getTouchedZone( } /** * A stateful composable that manages the size and position of a resizable RegionBox. * A class that encapsulates the state and logic for the RegionBox composable. * * @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 offset, width, and height * when the user finishes a drag gesture. * @param initialOffset The initial top-left offset of the box. Default is (0, 0), which is the * parent's top-left corner. * @param modifier The modifier to be applied to the composable. * @param minSizePx The minimum size of the box in pixels. * @param touchAreaPx The size of the touch area for resizing in pixels. */ @Composable fun RegionBox( initialWidth: Dp, initialHeight: Dp, onDragEnd: (offset: Offset, width: Dp, height: Dp) -> Unit, drawableLoaderViewModel: DrawableLoaderViewModel, initialOffset: Offset = Offset.Zero, modifier: Modifier = Modifier, ) { // The minimum size allowed for the box. val minSize = 1.dp private class RegionBoxState(private val minSizePx: Float, private val touchAreaPx: Float) { var rect by mutableStateOf<Rect?>(null) private set val density = LocalDensity.current val minSizePx = remember(density) { with(density) { minSize.toPx() } } private var dragMode by mutableStateOf(DragMode.NONE) private var resizeZone by mutableStateOf<ResizeZone?>(null) // State for the region box's geometry. var rect by remember { mutableStateOf( with(density) { // offset is how far from the parent's top-left corner the box should be placed. Rect(offset = initialOffset, size = Size(initialWidth.toPx(), initialHeight.toPx())) } // The offset of the initial press when the user starts a drag gesture. private var newBoxStartOffset by mutableStateOf(Offset.Zero) // Must remember the screen size for the drag logic. Initial values are set to 0. var screenWidth by mutableStateOf(0f) var screenHeight by mutableStateOf(0f) fun startDrag(startOffset: Offset) { val currentRect = rect if (currentRect == null) { // If the box is not yet created, it is a drawing drag. dragMode = DragMode.DRAWING newBoxStartOffset = startOffset } else { // The offset of the existing box. val currentRectOffset = startOffset - currentRect.topLeft val touchedZone = getTouchedZone( currentRect.width, currentRect.height, currentRectOffset, touchAreaPx, ) when { touchedZone != null -> { // If the drag was initiated within the current rectangle's drag-to-resize touch // zone, it is a resizing drag. dragMode = DragMode.RESIZING resizeZone = touchedZone } currentRect.contains(startOffset) -> { // If the drag was initiated inside the rectangle and not within the touch // zones, it is a moving drag. dragMode = DragMode.MOVING } else -> { // The touch was initiated outside of the rectangle and its touch zone. dragMode = DragMode.DRAWING newBoxStartOffset = startOffset } } } } val onBoxDrag: (dragAmount: Offset, maxWidth: Float, maxHeight: Float) -> Unit = { dragAmount, maxWidth, maxHeight -> val newOffset = rect.topLeft + dragAmount fun drag(endOffset: Offset, dragAmount: Offset) { val currentRect = rect when (dragMode) { DragMode.DRAWING -> { // Ensure that the box remains within the boundaries of the screen. val newBoxEndOffset = Offset( x = endOffset.x.coerceIn(0f, screenWidth), y = endOffset.y.coerceIn(0f, screenHeight), ) rect = Rect( left = min(newBoxStartOffset.x, newBoxEndOffset.x), top = min(newBoxStartOffset.y, newBoxEndOffset.y), right = max(newBoxStartOffset.x, newBoxEndOffset.x), bottom = max(newBoxStartOffset.y, newBoxEndOffset.y), ) } DragMode.MOVING -> { if (currentRect != null) { val newOffset = currentRect.topLeft + dragAmount // Constrain the new position within the parent's boundaries val constrainedLeft: Float = newOffset.x.coerceIn(0f, maxWidth - rect.width) val constrainedTop: Float = newOffset.y.coerceIn(0f, maxHeight - rect.height) val constrainedLeft = newOffset.x.coerceIn(0f, screenWidth - currentRect.width) val constrainedTop = newOffset.y.coerceIn(0f, screenHeight - currentRect.height) rect = rect.translate( translateX = constrainedLeft - rect.left, translateY = constrainedTop - rect.top, currentRect.translate( translateX = constrainedLeft - currentRect.left, translateY = constrainedTop - currentRect.top, ) } ResizableRectangle( rect = rect, onResizeDrag = { dragAmount, zone, maxWidth, maxHeight -> rect = zone.processResizeDrag(rect, dragAmount, minSizePx, maxWidth, maxHeight) }, onBoxDrag = onBoxDrag, onDragEnd = { onDragEnd( Offset(rect.left, rect.top), with(density) { rect.width.toDp() }, with(density) { rect.height.toDp() }, ) }, drawableLoaderViewModel = drawableLoaderViewModel, modifier = modifier, } DragMode.RESIZING -> { if (currentRect != null && resizeZone != null) { rect = resizeZone!!.processResizeDrag( currentRect, dragAmount, minSizePx, screenWidth, screenHeight, ) } } DragMode.NONE -> { // Do nothing. } } } fun dragEnd() { dragMode = DragMode.NONE resizeZone = null } } /** * A box with a border that can be resized by dragging its zone (corner or edge), and moved by * dragging its body. * A composable that allows the user to create, move, resize, and redraw a rectangular region. * * @param rect The current geometry of the region box. * @param onResizeDrag Callback invoked when a corner or edge is dragged. * @param onBoxDrag Callback invoked when the main body of the box is dragged. * @param onDragEnd Callback invoked when a drag gesture finishes. * @param onRegionSelected A callback function that is invoked with the final rectangle when the * user finishes a drag gesture. This rectangle is used for taking a screenshot. The rectangle is * of type [android.graphics.Rect] because the screenshot API requires int values. * @param drawableLoaderViewModel The view model that is used to load drawables. * @param modifier The modifier to be applied to the composable. */ @Composable private fun ResizableRectangle( rect: Rect, onResizeDrag: (dragAmount: Offset, zone: ResizeZone, maxWidth: Float, maxHeight: Float) -> Unit, onBoxDrag: (dragAmount: Offset, maxWidth: Float, maxHeight: Float) -> Unit, onDragEnd: () -> Unit, fun RegionBox( onRegionSelected: (rect: IntRect) -> Unit, drawableLoaderViewModel: DrawableLoaderViewModel, modifier: Modifier = Modifier, ) { // The width of the border stroke around the region box. val borderStrokeWidth = 4.dp // The touch area for detecting an edge or corner resize drag. val touchArea = 48.dp val density = LocalDensity.current // Must remember the screen size for the drag logic. Initial values are set to 0. var screenWidth by remember { mutableStateOf(0f) } var screenHeight by remember { mutableStateOf(0f) } // The minimum size allowed for the box. val minSize = 1.dp val minSizePx = remember(density) { with(density) { minSize.toPx() } } val density = LocalDensity.current val touchAreaPx = with(density) { touchArea.toPx() } // The touch area for detecting an edge or corner resize drag. val touchArea = 48.dp val touchAreaPx = remember(density) { with(density) { touchArea.toPx() } } // The zone being dragged for resizing, if any. var draggedZone by remember { mutableStateOf<ResizeZone?>(null) } val state = remember { RegionBoxState(minSizePx, touchAreaPx) } Box( modifier = Loading @@ -190,53 +253,51 @@ private fun ResizableRectangle( .fillMaxSize() // .onSizeChanged gives us the final size of this box, which is the screen size, // after it has been drawn. .onSizeChanged { sizeInPixels -> screenWidth = sizeInPixels.width.toFloat() screenHeight = sizeInPixels.height.toFloat() .onSizeChanged { sizeInPixels: IntSize -> state.screenWidth = sizeInPixels.width.toFloat() state.screenHeight = sizeInPixels.height.toFloat() } ) { Box( modifier = Modifier.graphicsLayer(translationX = rect.left, translationY = rect.top) .size( width = with(density) { rect.width.toDp() }, height = with(density) { rect.height.toDp() }, ) .border(borderStrokeWidth, MaterialTheme.colorScheme.onSurfaceVariant) .pointerInput(screenWidth, screenHeight, onResizeDrag, onBoxDrag, onDragEnd) { .pointerInput(Unit) { detectDragGestures( onDragStart = { startOffset -> draggedZone = getTouchedZone( boxWidth = size.width.toFloat(), boxHeight = size.height.toFloat(), startOffset = startOffset, touchAreaPx = touchAreaPx, ) onDragStart = { startOffset: Offset -> state.startDrag(startOffset) }, onDrag = { change: PointerInputChange, dragAmount: Offset -> change.consume() state.drag(change.position, dragAmount) }, onDragEnd = { draggedZone = null onDragEnd() }, onDrag = { change, dragAmount -> change.consume() // Create a stable and local copy of the draggedZone. This // ensures that the value does not change in the onResizeDrag // callback. val currentZone = draggedZone if (currentZone != null) { // If currentZone has a value, it means we are dragging a zone // for resizing. onResizeDrag(dragAmount, currentZone, screenWidth, screenHeight) } else { // If currentZone is null, it means we are dragging the box. onBoxDrag(dragAmount, screenWidth, screenHeight) state.dragEnd() state.rect?.let { rect: Rect -> // Store the rectangle to the ViewModel for taking a screenshot. // The screenshot API requires a Rect class with int values. onRegionSelected( IntRect( rect.left.roundToInt(), rect.top.roundToInt(), rect.right.roundToInt(), rect.bottom.roundToInt(), ) ) } }, onDragCancel = { state.dragEnd() }, ) }, } ) { // The width of the border stroke around the region box. val borderStrokeWidth = 4.dp state.rect?.let { currentRect -> Box( modifier = Modifier.graphicsLayer( translationX = currentRect.left, translationY = currentRect.top, ) .size( width = with(density) { currentRect.width.toDp() }, height = with(density) { currentRect.height.toDp() }, ) .border(borderStrokeWidth, MaterialTheme.colorScheme.onSurfaceVariant), contentAlignment = Alignment.Center, ) { PrimaryButton( Loading @@ -254,3 +315,4 @@ private fun ResizableRectangle( } } } } Loading
packages/SystemUI/src/com/android/systemui/screencapture/record/largescreen/ui/compose/PreCaptureUI.kt +2 −7 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.systemui.screencapture.record.largescreen.ui.compose import android.graphics.Rect import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth Loading @@ -24,9 +25,7 @@ import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import com.android.systemui.res.R Loading Loading @@ -74,11 +73,7 @@ fun PreCaptureUI(viewModel: PreCaptureViewModel) { // TODO(b/427541309) Set the initial width and height of the RegionBox based on the // viewmodel state. RegionBox( initialWidth = 100.dp, initialHeight = 100.dp, onDragEnd = { _: Offset, _: Dp, _: Dp -> // TODO(b/427541309) Update the region box in the viewmodel. }, onRegionSelected = { rect: Rect -> viewModel.updateRegionBox(rect) }, drawableLoaderViewModel = viewModel, ) } Loading
packages/SystemUI/src/com/android/systemui/screencapture/record/largescreen/ui/compose/RegionBox.kt +197 −135 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.systemui.screencapture.record.largescreen.ui.compose import android.graphics.Rect as IntRect import androidx.compose.foundation.border import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.layout.Box Loading @@ -31,18 +32,29 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import com.android.systemui.res.R import com.android.systemui.screencapture.common.ui.compose.PrimaryButton import com.android.systemui.screencapture.common.ui.compose.loadIcon import com.android.systemui.screencapture.common.ui.viewmodel.DrawableLoaderViewModel import kotlin.math.max import kotlin.math.min import kotlin.math.roundToInt // The different modes of interaction that the user can have with the RegionBox. private enum class DragMode { DRAWING, MOVING, RESIZING, NONE, } /** * Determines which zone (corner or edge) of a box is being touched based on the press offset. Loading @@ -59,6 +71,18 @@ private fun getTouchedZone( startOffset: Offset, touchAreaPx: Float, ): ResizeZone? { // Check if the touch is within the touch area of the box. val touchedZone = Rect( left = -touchAreaPx, top = -touchAreaPx, right = boxWidth + touchAreaPx, bottom = boxHeight + touchAreaPx, ) if (!touchedZone.contains(startOffset)) { return null } val isTouchingTop = startOffset.y in -touchAreaPx..touchAreaPx val isTouchingBottom = startOffset.y in (boxHeight - touchAreaPx)..(boxHeight + touchAreaPx) val isTouchingLeft = startOffset.x in -touchAreaPx..touchAreaPx Loading @@ -82,107 +106,146 @@ private fun getTouchedZone( } /** * A stateful composable that manages the size and position of a resizable RegionBox. * A class that encapsulates the state and logic for the RegionBox composable. * * @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 offset, width, and height * when the user finishes a drag gesture. * @param initialOffset The initial top-left offset of the box. Default is (0, 0), which is the * parent's top-left corner. * @param modifier The modifier to be applied to the composable. * @param minSizePx The minimum size of the box in pixels. * @param touchAreaPx The size of the touch area for resizing in pixels. */ @Composable fun RegionBox( initialWidth: Dp, initialHeight: Dp, onDragEnd: (offset: Offset, width: Dp, height: Dp) -> Unit, drawableLoaderViewModel: DrawableLoaderViewModel, initialOffset: Offset = Offset.Zero, modifier: Modifier = Modifier, ) { // The minimum size allowed for the box. val minSize = 1.dp private class RegionBoxState(private val minSizePx: Float, private val touchAreaPx: Float) { var rect by mutableStateOf<Rect?>(null) private set val density = LocalDensity.current val minSizePx = remember(density) { with(density) { minSize.toPx() } } private var dragMode by mutableStateOf(DragMode.NONE) private var resizeZone by mutableStateOf<ResizeZone?>(null) // State for the region box's geometry. var rect by remember { mutableStateOf( with(density) { // offset is how far from the parent's top-left corner the box should be placed. Rect(offset = initialOffset, size = Size(initialWidth.toPx(), initialHeight.toPx())) } // The offset of the initial press when the user starts a drag gesture. private var newBoxStartOffset by mutableStateOf(Offset.Zero) // Must remember the screen size for the drag logic. Initial values are set to 0. var screenWidth by mutableStateOf(0f) var screenHeight by mutableStateOf(0f) fun startDrag(startOffset: Offset) { val currentRect = rect if (currentRect == null) { // If the box is not yet created, it is a drawing drag. dragMode = DragMode.DRAWING newBoxStartOffset = startOffset } else { // The offset of the existing box. val currentRectOffset = startOffset - currentRect.topLeft val touchedZone = getTouchedZone( currentRect.width, currentRect.height, currentRectOffset, touchAreaPx, ) when { touchedZone != null -> { // If the drag was initiated within the current rectangle's drag-to-resize touch // zone, it is a resizing drag. dragMode = DragMode.RESIZING resizeZone = touchedZone } currentRect.contains(startOffset) -> { // If the drag was initiated inside the rectangle and not within the touch // zones, it is a moving drag. dragMode = DragMode.MOVING } else -> { // The touch was initiated outside of the rectangle and its touch zone. dragMode = DragMode.DRAWING newBoxStartOffset = startOffset } } } } val onBoxDrag: (dragAmount: Offset, maxWidth: Float, maxHeight: Float) -> Unit = { dragAmount, maxWidth, maxHeight -> val newOffset = rect.topLeft + dragAmount fun drag(endOffset: Offset, dragAmount: Offset) { val currentRect = rect when (dragMode) { DragMode.DRAWING -> { // Ensure that the box remains within the boundaries of the screen. val newBoxEndOffset = Offset( x = endOffset.x.coerceIn(0f, screenWidth), y = endOffset.y.coerceIn(0f, screenHeight), ) rect = Rect( left = min(newBoxStartOffset.x, newBoxEndOffset.x), top = min(newBoxStartOffset.y, newBoxEndOffset.y), right = max(newBoxStartOffset.x, newBoxEndOffset.x), bottom = max(newBoxStartOffset.y, newBoxEndOffset.y), ) } DragMode.MOVING -> { if (currentRect != null) { val newOffset = currentRect.topLeft + dragAmount // Constrain the new position within the parent's boundaries val constrainedLeft: Float = newOffset.x.coerceIn(0f, maxWidth - rect.width) val constrainedTop: Float = newOffset.y.coerceIn(0f, maxHeight - rect.height) val constrainedLeft = newOffset.x.coerceIn(0f, screenWidth - currentRect.width) val constrainedTop = newOffset.y.coerceIn(0f, screenHeight - currentRect.height) rect = rect.translate( translateX = constrainedLeft - rect.left, translateY = constrainedTop - rect.top, currentRect.translate( translateX = constrainedLeft - currentRect.left, translateY = constrainedTop - currentRect.top, ) } ResizableRectangle( rect = rect, onResizeDrag = { dragAmount, zone, maxWidth, maxHeight -> rect = zone.processResizeDrag(rect, dragAmount, minSizePx, maxWidth, maxHeight) }, onBoxDrag = onBoxDrag, onDragEnd = { onDragEnd( Offset(rect.left, rect.top), with(density) { rect.width.toDp() }, with(density) { rect.height.toDp() }, ) }, drawableLoaderViewModel = drawableLoaderViewModel, modifier = modifier, } DragMode.RESIZING -> { if (currentRect != null && resizeZone != null) { rect = resizeZone!!.processResizeDrag( currentRect, dragAmount, minSizePx, screenWidth, screenHeight, ) } } DragMode.NONE -> { // Do nothing. } } } fun dragEnd() { dragMode = DragMode.NONE resizeZone = null } } /** * A box with a border that can be resized by dragging its zone (corner or edge), and moved by * dragging its body. * A composable that allows the user to create, move, resize, and redraw a rectangular region. * * @param rect The current geometry of the region box. * @param onResizeDrag Callback invoked when a corner or edge is dragged. * @param onBoxDrag Callback invoked when the main body of the box is dragged. * @param onDragEnd Callback invoked when a drag gesture finishes. * @param onRegionSelected A callback function that is invoked with the final rectangle when the * user finishes a drag gesture. This rectangle is used for taking a screenshot. The rectangle is * of type [android.graphics.Rect] because the screenshot API requires int values. * @param drawableLoaderViewModel The view model that is used to load drawables. * @param modifier The modifier to be applied to the composable. */ @Composable private fun ResizableRectangle( rect: Rect, onResizeDrag: (dragAmount: Offset, zone: ResizeZone, maxWidth: Float, maxHeight: Float) -> Unit, onBoxDrag: (dragAmount: Offset, maxWidth: Float, maxHeight: Float) -> Unit, onDragEnd: () -> Unit, fun RegionBox( onRegionSelected: (rect: IntRect) -> Unit, drawableLoaderViewModel: DrawableLoaderViewModel, modifier: Modifier = Modifier, ) { // The width of the border stroke around the region box. val borderStrokeWidth = 4.dp // The touch area for detecting an edge or corner resize drag. val touchArea = 48.dp val density = LocalDensity.current // Must remember the screen size for the drag logic. Initial values are set to 0. var screenWidth by remember { mutableStateOf(0f) } var screenHeight by remember { mutableStateOf(0f) } // The minimum size allowed for the box. val minSize = 1.dp val minSizePx = remember(density) { with(density) { minSize.toPx() } } val density = LocalDensity.current val touchAreaPx = with(density) { touchArea.toPx() } // The touch area for detecting an edge or corner resize drag. val touchArea = 48.dp val touchAreaPx = remember(density) { with(density) { touchArea.toPx() } } // The zone being dragged for resizing, if any. var draggedZone by remember { mutableStateOf<ResizeZone?>(null) } val state = remember { RegionBoxState(minSizePx, touchAreaPx) } Box( modifier = Loading @@ -190,53 +253,51 @@ private fun ResizableRectangle( .fillMaxSize() // .onSizeChanged gives us the final size of this box, which is the screen size, // after it has been drawn. .onSizeChanged { sizeInPixels -> screenWidth = sizeInPixels.width.toFloat() screenHeight = sizeInPixels.height.toFloat() .onSizeChanged { sizeInPixels: IntSize -> state.screenWidth = sizeInPixels.width.toFloat() state.screenHeight = sizeInPixels.height.toFloat() } ) { Box( modifier = Modifier.graphicsLayer(translationX = rect.left, translationY = rect.top) .size( width = with(density) { rect.width.toDp() }, height = with(density) { rect.height.toDp() }, ) .border(borderStrokeWidth, MaterialTheme.colorScheme.onSurfaceVariant) .pointerInput(screenWidth, screenHeight, onResizeDrag, onBoxDrag, onDragEnd) { .pointerInput(Unit) { detectDragGestures( onDragStart = { startOffset -> draggedZone = getTouchedZone( boxWidth = size.width.toFloat(), boxHeight = size.height.toFloat(), startOffset = startOffset, touchAreaPx = touchAreaPx, ) onDragStart = { startOffset: Offset -> state.startDrag(startOffset) }, onDrag = { change: PointerInputChange, dragAmount: Offset -> change.consume() state.drag(change.position, dragAmount) }, onDragEnd = { draggedZone = null onDragEnd() }, onDrag = { change, dragAmount -> change.consume() // Create a stable and local copy of the draggedZone. This // ensures that the value does not change in the onResizeDrag // callback. val currentZone = draggedZone if (currentZone != null) { // If currentZone has a value, it means we are dragging a zone // for resizing. onResizeDrag(dragAmount, currentZone, screenWidth, screenHeight) } else { // If currentZone is null, it means we are dragging the box. onBoxDrag(dragAmount, screenWidth, screenHeight) state.dragEnd() state.rect?.let { rect: Rect -> // Store the rectangle to the ViewModel for taking a screenshot. // The screenshot API requires a Rect class with int values. onRegionSelected( IntRect( rect.left.roundToInt(), rect.top.roundToInt(), rect.right.roundToInt(), rect.bottom.roundToInt(), ) ) } }, onDragCancel = { state.dragEnd() }, ) }, } ) { // The width of the border stroke around the region box. val borderStrokeWidth = 4.dp state.rect?.let { currentRect -> Box( modifier = Modifier.graphicsLayer( translationX = currentRect.left, translationY = currentRect.top, ) .size( width = with(density) { currentRect.width.toDp() }, height = with(density) { currentRect.height.toDp() }, ) .border(borderStrokeWidth, MaterialTheme.colorScheme.onSurfaceVariant), contentAlignment = Alignment.Center, ) { PrimaryButton( Loading @@ -254,3 +315,4 @@ private fun ResizableRectangle( } } } }