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

Commit f28baa62 authored by Nikki Moteva's avatar Nikki Moteva
Browse files

System UI: Change corner resizability logic for region select.

changes in this CL:
- the knobs on the corners were removed as part of the UX change.
- the corners now have the ability to resize without depending on the knobs.
- the minimum size of the box is confirmed to be 1dp.

Screencast: http://shortn/_tfXqWcCjUe

Bug: 423965332,422565042,423964850
Test: Manual
Flag: com.android.systemui.desktop_screen_capture
Change-Id: I690cc48021c5353cf15c4abb9560dbd659cf36b5
parent 1247309c
Loading
Loading
Loading
Loading
+85 −86
Original line number Original line Diff line number Diff line
@@ -16,14 +16,12 @@


package com.android.systemui.screencapture.ui.compose
package com.android.systemui.screencapture.ui.compose


import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.border
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.getValue
@@ -57,6 +55,35 @@ enum class Corner(val alignment: Alignment) {
    BottomRight(Alignment.BottomEnd),
    BottomRight(Alignment.BottomEnd),
}
}


/**
 * Determines which corner 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.
 */
private fun getDraggedCorner(
    boxWidth: Float,
    boxHeight: Float,
    startOffset: Offset,
    touchAreaPx: Float,
): Corner? {
    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
    }
}

/**
/**
 * A stateful composable that manages the size and position of a resizable RegionBox.
 * A stateful composable that manages the size and position of a resizable RegionBox.
 *
 *
@@ -76,9 +103,8 @@ fun RegionBox(
    initialOffset: Offset = Offset.Zero,
    initialOffset: Offset = Offset.Zero,
    modifier: Modifier = Modifier,
    modifier: Modifier = Modifier,
) {
) {
    // The minimum size allowed for the rectangle.
    // The minimum size allowed for the box.
    // TODO(b/422565042): change when its value is finalized.
    val minSize = 1.dp
    val minSize = 48.dp


    val density = LocalDensity.current
    val density = LocalDensity.current
    val minSizePx = remember(density) { with(density) { minSize.toPx() } }
    val minSizePx = remember(density) { with(density) { minSize.toPx() } }
@@ -164,10 +190,10 @@ fun RegionBox(
}
}


/**
/**
 * A box with border lines and centered corner knobs that can be resized and dragged.
 * A box with a border that can be resized by dragging its corners and moved by dragging its body.
 *
 *
 * @param rect The current geometry of the region box.
 * @param rect The current geometry of the region box.
 * @param onCornerDrag Callback invoked when a corner knob is dragged.
 * @param onCornerDrag Callback invoked when a corner is dragged.
 * @param onBoxDrag Callback invoked when the main body of the box 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 onDragEnd Callback invoked when a drag gesture finishes.
 * @param modifier The modifier to be applied to the composable.
 * @param modifier The modifier to be applied to the composable.
@@ -185,18 +211,21 @@ private fun ResizableRectangle(
    val screenshotIcon =
    val screenshotIcon =
        IconModel.Resource(res = R.drawable.ic_screen_capture_camera, contentDescription = null)
        IconModel.Resource(res = R.drawable.ic_screen_capture_camera, contentDescription = null)


    // The diameter of the resizable knob on each corner of the region box.
    val knobDiameter = 8.dp
    // The width of the border stroke around the region box.
    // The width of the border stroke around the region box.
    val borderStrokeWidth = 4.dp
    val borderStrokeWidth = 4.dp
    // The touch area for detecting a corner resize drag.
    val cornerTouchArea = 48.dp


    // Must remember the screen size for the drag logic. Initial values are set to 0.
    // Must remember the screen size for the drag logic. Initial values are set to 0.
    var screenWidth by remember { mutableStateOf(0f) }
    var screenWidth by remember { mutableStateOf(0f) }
    var screenHeight by remember { mutableStateOf(0f) }
    var screenHeight by remember { mutableStateOf(0f) }


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

    // State of the corner, can be either dragged or null.
    var draggedCorner by remember { mutableStateOf<Corner?>(null) }


    // The box that contains the whole screen.
    Box(
    Box(
        modifier =
        modifier =
            modifier
            modifier
@@ -208,7 +237,6 @@ private fun ResizableRectangle(
                    screenHeight = sizeInPixels.height.toFloat()
                    screenHeight = sizeInPixels.height.toFloat()
                }
                }
    ) {
    ) {
        // The box container for the region box and its knobs.
        Box(
        Box(
            modifier =
            modifier =
                Modifier.graphicsLayer(translationX = rect.left, translationY = rect.top)
                Modifier.graphicsLayer(translationX = rect.left, translationY = rect.top)
@@ -216,18 +244,43 @@ private fun ResizableRectangle(
                        width = with(density) { rect.width.toDp() },
                        width = with(density) { rect.width.toDp() },
                        height = with(density) { rect.height.toDp() },
                        height = with(density) { rect.height.toDp() },
                    )
                    )
        ) {
            // The main box for the region selection.
            Box(
                modifier =
                    Modifier.fillMaxSize()
                    .border(borderStrokeWidth, MaterialTheme.colorScheme.onSurfaceVariant)
                    .border(borderStrokeWidth, MaterialTheme.colorScheme.onSurfaceVariant)
                        .pointerInput(screenWidth, screenHeight, onBoxDrag, onDragEnd) {
                    .pointerInput(screenWidth, screenHeight, onCornerDrag, onBoxDrag, onDragEnd) {
                        detectDragGestures(
                        detectDragGestures(
                                onDragEnd = onDragEnd,
                            onDragStart = { startOffset ->
                                draggedCorner =
                                    getDraggedCorner(
                                        boxWidth = size.width.toFloat(),
                                        boxHeight = size.height.toFloat(),
                                        startOffset = startOffset,
                                        touchAreaPx = cornerTouchAreaPx,
                                    )
                            },
                            onDragEnd = {
                                draggedCorner = null
                                onDragEnd()
                            },
                            onDrag = { change, dragAmount ->
                            onDrag = { change, dragAmount ->
                                change.consume()
                                change.consume()

                                // Create a stable and local copy of the dragged corner. This
                                // ensures that the value does not change in the onCornerDrag
                                // 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,
                                    )
                                } else {
                                    // If 'currentCorner' is null, it means we are dragging the box.
                                    onBoxDrag(dragAmount, screenWidth, screenHeight)
                                    onBoxDrag(dragAmount, screenWidth, screenHeight)
                                }
                            },
                            },
                        )
                        )
                    },
                    },
@@ -241,59 +294,5 @@ private fun ResizableRectangle(
                icon = screenshotIcon,
                icon = screenshotIcon,
            )
            )
        }
        }

            // The offset is half of the knob diameter so that it is centered.
            val knobOffset = knobDiameter / 2

            // Create knobs by looping through the Corner enum values
            Corner.entries.forEach { corner ->
                val xOffset: Dp
                val yOffset: Dp

                if (corner == Corner.TopLeft || corner == Corner.BottomLeft) {
                    xOffset = -knobOffset
                } else {
                    xOffset = knobOffset
                }

                if (corner == Corner.TopLeft || corner == Corner.TopRight) {
                    yOffset = -knobOffset
                } else {
                    yOffset = knobOffset
                }

                Knob(
                    diameter = knobDiameter,
                    modifier =
                        Modifier.align(corner.alignment)
                            .offset(x = xOffset, y = yOffset)
                            .pointerInput(corner, screenWidth, screenHeight, onCornerDrag, onDragEnd) {
                                detectDragGestures(
                                    onDragEnd = onDragEnd,
                                    onDrag = { change, dragAmount ->
                                        change.consume()
                                        onCornerDrag(dragAmount, corner, screenWidth, screenHeight)
                                    },
                                )
                            },
                )
            }
        }
    }
    }
}
}

/**
 * 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) {
    Box(
        modifier =
            modifier
                .size(diameter)
                .background(color = MaterialTheme.colorScheme.onSurface, shape = CircleShape)
    )
}