Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 447024da authored by Nikki Moteva's avatar Nikki Moteva Committed by Android (Google) Code Review
Browse files

Merge "System UI: Enable resizing edge for screen capture's region select box." into main

parents e95dcb56 7039007b
Loading
Loading
Loading
Loading
+99 −59
Original line number Diff line number Diff line
@@ -43,45 +43,70 @@ import androidx.compose.ui.unit.dp
import com.android.systemui.common.shared.model.Icon as IconModel
import com.android.systemui.res.R

/**
 * A common interface for all draggable zones of the resizable box. The ResizeZone is one of Corner
 * or Edge.
 */
sealed interface ResizeZone

/**
 * An enum to identify each of the four corners of the rectangle.
 *
 * @param alignment The alignment of the corner within the box.
 */
enum class Corner(val alignment: Alignment) {
enum class Corner(val alignment: Alignment) : ResizeZone {
    TopLeft(Alignment.TopStart),
    TopRight(Alignment.TopEnd),
    BottomLeft(Alignment.BottomStart),
    BottomRight(Alignment.BottomEnd),
}

/** An enum to identify each of the four edges of the rectangle. */
enum class Edge : ResizeZone {
    Top,
    Bottom,
    Left,
    Right,
}

/**
 * Determines which corner of a box is being touched based on the press offset.
 * Determines which zone (corner or edge) of a box is being touched based on the press offset.
 *
 * @param boxWidth The total width of the box.
 * @param boxHeight The total height of the box.
 * @param startOffset The position of the initial press.
 * @param touchAreaPx The size of the touch area in pixels.
 * @return The Corner that was touched, or `null` if the press was not in a corner.
 * @return The ResizeZone that was pressed, or `null` if the press was not on a zone.
 */
private fun getDraggedCorner(
private fun getDragZone(
    boxWidth: Float,
    boxHeight: Float,
    startOffset: Offset,
    touchAreaPx: Float,
): Corner? {
): ResizeZone? {
    val isTouchingTop = startOffset.y in -touchAreaPx..touchAreaPx
    val isTouchingBottom = startOffset.y in (boxHeight - touchAreaPx)..boxHeight
    val isTouchingLeft = startOffset.x in -touchAreaPx..touchAreaPx
    val isTouchingRight = startOffset.x in (boxWidth - touchAreaPx)..boxWidth

    return when {
        isTouchingTop && isTouchingLeft -> Corner.TopLeft
        isTouchingTop && isTouchingRight -> Corner.TopRight
        isTouchingBottom && isTouchingLeft -> Corner.BottomLeft
        isTouchingBottom && isTouchingRight -> Corner.BottomRight
        else -> null
    // Corners should be checked first because edges are also touched when the user touches a
    // corner.
    when {
        isTouchingTop && isTouchingLeft -> return Corner.TopLeft
        isTouchingTop && isTouchingRight -> return Corner.TopRight
        isTouchingBottom && isTouchingLeft -> return Corner.BottomLeft
        isTouchingBottom && isTouchingRight -> return Corner.BottomRight
    }

    // Edges are checked next.
    when {
        isTouchingTop -> return Edge.Top
        isTouchingBottom -> return Edge.Bottom
        isTouchingLeft -> return Edge.Left
        isTouchingRight -> return Edge.Right
    }

    return null
}

/**
@@ -119,10 +144,9 @@ fun RegionBox(
        )
    }

    val onCornerDrag:
        (dragAmount: Offset, corner: Corner, maxWidth: Float, maxHeight: Float) -> Unit =
        { dragAmount, corner, maxWidth, maxHeight ->
            // Used for calculating the new dimensions based on which corner is dragged.
    val onResizeDrag:
        (dragAmount: Offset, zone: ResizeZone, maxWidth: Float, maxHeight: Float) -> Unit =
        { dragAmount, zone, maxWidth, maxHeight ->
            var newLeft = rect.left
            var newTop = rect.top
            var newRight = rect.right
@@ -131,30 +155,50 @@ fun RegionBox(
            val (dragX, dragY) = dragAmount

            // Handle horizontal drag for resizing.
            if (corner == Corner.TopLeft || corner == Corner.BottomLeft) {
            when (zone) {
                Corner.TopLeft,
                Corner.BottomLeft,
                Edge.Left -> {
                    val potentialNewLeft = rect.left + dragX
                    val rightLimitForMinWidth = rect.right - minSizePx

                    newLeft = potentialNewLeft.coerceIn(0f, rightLimitForMinWidth)
            } else {
                }
                Corner.TopRight,
                Corner.BottomRight,
                Edge.Right -> {
                    val potentialNewRight = rect.right + dragX
                    val leftLimitForMinWidth = rect.left + minSizePx

                    newRight = potentialNewRight.coerceIn(leftLimitForMinWidth, maxWidth)
                }
                else -> {
                    // No horizontal change for Top or Bottom edges.
                }
            }

            // Handle vertical drag for resizing.
            if (corner == Corner.TopLeft || corner == Corner.TopRight) {
            when (zone) {
                Corner.TopLeft,
                Corner.TopRight,
                Edge.Top -> {
                    val potentialNewTop = rect.top + dragY
                    val bottomLimitForMinHeight = rect.bottom - minSizePx

                    newTop = potentialNewTop.coerceIn(0f, bottomLimitForMinHeight)
            } else {
                }
                Corner.BottomLeft,
                Corner.BottomRight,
                Edge.Bottom -> {
                    val potentialNewBottom = rect.bottom + dragY
                    val topLimitForMinHeight = rect.top + minSizePx

                    newBottom = potentialNewBottom.coerceIn(topLimitForMinHeight, maxHeight)
                }
                else -> {
                    // No vertical change for Left or Right edges.
                }
            }

            rect = Rect(newLeft, newTop, newRight, newBottom)
        }
@@ -176,7 +220,7 @@ fun RegionBox(

    ResizableRectangle(
        rect = rect,
        onCornerDrag = onCornerDrag,
        onResizeDrag = onResizeDrag,
        onBoxDrag = onBoxDrag,
        onDragEnd = {
            onDragEnd(
@@ -190,10 +234,11 @@ fun RegionBox(
}

/**
 * A box with a border that can be resized by dragging its corners and moved by dragging its body.
 * A box with a border that can be resized by dragging its zone (corner or edge), and moved by
 * dragging its body.
 *
 * @param rect The current geometry of the region box.
 * @param onCornerDrag Callback invoked when a corner is dragged.
 * @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 modifier The modifier to be applied to the composable.
@@ -201,7 +246,7 @@ fun RegionBox(
@Composable
private fun ResizableRectangle(
    rect: Rect,
    onCornerDrag: (dragAmount: Offset, corner: Corner, maxWidth: Float, maxHeight: Float) -> Unit,
    onResizeDrag: (dragAmount: Offset, zone: ResizeZone, maxWidth: Float, maxHeight: Float) -> Unit,
    onBoxDrag: (dragAmount: Offset, maxWidth: Float, maxHeight: Float) -> Unit,
    onDragEnd: () -> Unit,
    modifier: Modifier = Modifier,
@@ -213,18 +258,18 @@ private fun ResizableRectangle(

    // The width of the border stroke around the region box.
    val borderStrokeWidth = 4.dp
    // The touch area for detecting a corner resize drag.
    val cornerTouchArea = 48.dp
    // The touch area for detecting an edge or corner resize drag.
    val touchArea = 48.dp

    // 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) }

    val density = LocalDensity.current
    val cornerTouchAreaPx = with(density) { cornerTouchArea.toPx() }
    val touchAreaPx = with(density) { touchArea.toPx() }

    // State of the corner, can be either dragged or null.
    var draggedCorner by remember { mutableStateOf<Corner?>(null) }
    // The zone being dragged for resizing, if any.
    var draggedZone by remember { mutableStateOf<ResizeZone?>(null) }

    Box(
        modifier =
@@ -245,40 +290,35 @@ private fun ResizableRectangle(
                        height = with(density) { rect.height.toDp() },
                    )
                    .border(borderStrokeWidth, MaterialTheme.colorScheme.onSurfaceVariant)
                    .pointerInput(screenWidth, screenHeight, onCornerDrag, onBoxDrag, onDragEnd) {
                    .pointerInput(screenWidth, screenHeight, onResizeDrag, onBoxDrag, onDragEnd) {
                        detectDragGestures(
                            onDragStart = { startOffset ->
                                draggedCorner =
                                    getDraggedCorner(
                                draggedZone =
                                    getDragZone(
                                        boxWidth = size.width.toFloat(),
                                        boxHeight = size.height.toFloat(),
                                        startOffset = startOffset,
                                        touchAreaPx = cornerTouchAreaPx,
                                        touchAreaPx = touchAreaPx,
                                    )
                            },
                            onDragEnd = {
                                draggedCorner = null
                                draggedZone = null
                                onDragEnd()
                            },
                            onDrag = { change, dragAmount ->
                                change.consume()

                                // Create a stable and local copy of the dragged corner. This
                                // ensures that the value does not change in the onCornerDrag
                                // Create a stable and local copy of the draggedZone. This
                                // ensures that the value does not change in the onResizeDrag
                                // callback.
                                val currentCorner = draggedCorner

                                if (currentCorner != null) {
                                    // If 'currentCorner' has a value, it means we are dragging a
                                    // corner for resizing.
                                    onCornerDrag(
                                        dragAmount,
                                        currentCorner,
                                        screenWidth,
                                        screenHeight,
                                    )
                                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 'currentCorner' is null, it means we are dragging the box.
                                    // If currentZone is null, it means we are dragging the box.
                                    onBoxDrag(dragAmount, screenWidth, screenHeight)
                                }
                            },