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

Commit 5c435f87 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "System UI: refactor screen capture's region box inferface" into main

parents 341ff134 d316ebf7
Loading
Loading
Loading
Loading
+22 −108
Original line number Diff line number Diff line
@@ -43,32 +43,6 @@ 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) : 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 zone (corner or edge) of a box is being touched based on the press offset.
 *
@@ -78,35 +52,32 @@ enum class Edge : ResizeZone {
 * @param touchAreaPx The size of the touch area in pixels.
 * @return The ResizeZone that was pressed, or `null` if the press was not on a zone.
 */
private fun getDragZone(
private fun getTouchedZone(
    boxWidth: Float,
    boxHeight: Float,
    startOffset: Offset,
    touchAreaPx: Float,
): ResizeZone? {
    val isTouchingTop = startOffset.y in -touchAreaPx..touchAreaPx
    val isTouchingBottom = startOffset.y in (boxHeight - touchAreaPx)..boxHeight
    val isTouchingBottom = startOffset.y in (boxHeight - touchAreaPx)..(boxHeight + touchAreaPx)
    val isTouchingLeft = startOffset.x in -touchAreaPx..touchAreaPx
    val isTouchingRight = startOffset.x in (boxWidth - touchAreaPx)..boxWidth
    val isTouchingRight = startOffset.x in (boxWidth - touchAreaPx)..(boxWidth + touchAreaPx)

    // 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
    }
    return when {
        // Corners have priority over edges, as they occupy overlapping areas.
        isTouchingTop && isTouchingLeft -> ResizeZone.Corner.TopLeft
        isTouchingTop && isTouchingRight -> ResizeZone.Corner.TopRight
        isTouchingBottom && isTouchingLeft -> ResizeZone.Corner.BottomLeft
        isTouchingBottom && isTouchingRight -> ResizeZone.Corner.BottomRight

    // Edges are checked next.
    when {
        isTouchingTop -> return Edge.Top
        isTouchingBottom -> return Edge.Bottom
        isTouchingLeft -> return Edge.Left
        isTouchingRight -> return Edge.Right
    }
        // If not a corner, check for edges.
        isTouchingLeft -> ResizeZone.Edge.Left
        isTouchingTop -> ResizeZone.Edge.Top
        isTouchingRight -> ResizeZone.Edge.Right
        isTouchingBottom -> ResizeZone.Edge.Bottom

    return null
        else -> null
    }
}

/**
@@ -144,65 +115,6 @@ fun RegionBox(
        )
    }

    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
            var newBottom = rect.bottom

            val (dragX, dragY) = dragAmount

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

                    newLeft = potentialNewLeft.coerceIn(0f, rightLimitForMinWidth)
                }
                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.
            when (zone) {
                Corner.TopLeft,
                Corner.TopRight,
                Edge.Top -> {
                    val potentialNewTop = rect.top + dragY
                    val bottomLimitForMinHeight = rect.bottom - minSizePx

                    newTop = potentialNewTop.coerceIn(0f, bottomLimitForMinHeight)
                }
                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)
        }

    val onBoxDrag: (dragAmount: Offset, maxWidth: Float, maxHeight: Float) -> Unit =
        { dragAmount, maxWidth, maxHeight ->
            val newOffset = rect.topLeft + dragAmount
@@ -220,7 +132,9 @@ fun RegionBox(

    ResizableRectangle(
        rect = rect,
        onResizeDrag = onResizeDrag,
        onResizeDrag = { dragAmount, zone, maxWidth, maxHeight ->
            rect = zone.processResizeDrag(rect, dragAmount, minSizePx, maxWidth, maxHeight)
        },
        onBoxDrag = onBoxDrag,
        onDragEnd = {
            onDragEnd(
@@ -294,7 +208,7 @@ private fun ResizableRectangle(
                        detectDragGestures(
                            onDragStart = { startOffset ->
                                draggedZone =
                                    getDragZone(
                                    getTouchedZone(
                                        boxWidth = size.width.toFloat(),
                                        boxHeight = size.height.toFloat(),
                                        startOffset = startOffset,
+191 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

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

import androidx.compose.ui.Alignment
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect

// Helper functions to handle the calculation for each side of the rectangle.
private fun newLeft(rect: Rect, dragAmount: Offset, minSizePx: Float): Float {
    return (rect.left + dragAmount.x).coerceIn(0f, rect.right - minSizePx)
}

private fun newTop(rect: Rect, dragAmount: Offset, minSizePx: Float): Float {
    return (rect.top + dragAmount.y).coerceIn(0f, rect.bottom - minSizePx)
}

private fun newRight(rect: Rect, dragAmount: Offset, minSizePx: Float, maxWidth: Float): Float {
    return (rect.right + dragAmount.x).coerceIn(rect.left + minSizePx, maxWidth)
}

private fun newBottom(rect: Rect, dragAmount: Offset, minSizePx: Float, maxHeight: Float): Float {
    return (rect.bottom + dragAmount.y).coerceIn(rect.top + minSizePx, maxHeight)
}

/**
 * Defines the different zones of the box that can be dragged for resizing. A zone could be a Corner
 * or Edge.
 */
sealed interface ResizeZone {

    /**
     * Processes a drag gesture and calculates the new geometry of the rectangle.
     *
     * @param rect The current geometry of the rectangle.
     * @param dragAmount The amount of drag in pixels.
     * @param minSizePx The minimum size of the rectangle in pixels.
     * @param maxWidth The maximum width constraint for the rectangle.
     * @param maxHeight The maximum height constraint for the rectangle.
     * @return The updated Rect after processing the drag.
     */
    fun processResizeDrag(
        rect: Rect,
        dragAmount: Offset,
        minSizePx: Float,
        maxWidth: Float,
        maxHeight: Float,
    ): Rect

    /**
     * A corner of the rectangle.
     *
     * @param alignment The alignment of the corner.
     */
    sealed interface Corner : ResizeZone {
        val alignment: Alignment

        data object TopLeft : Corner {
            override val alignment = Alignment.TopStart

            override fun processResizeDrag(
                rect: Rect,
                dragAmount: Offset,
                minSizePx: Float,
                maxWidth: Float,
                maxHeight: Float,
            ): Rect {
                val newLeft = newLeft(rect, dragAmount, minSizePx)
                val newTop = newTop(rect, dragAmount, minSizePx)
                return Rect(newLeft, newTop, rect.right, rect.bottom)
            }
        }

        data object TopRight : Corner {
            override val alignment = Alignment.TopEnd

            override fun processResizeDrag(
                rect: Rect,
                dragAmount: Offset,
                minSizePx: Float,
                maxWidth: Float,
                maxHeight: Float,
            ): Rect {
                val newRight = newRight(rect, dragAmount, minSizePx, maxWidth)
                val newTop = newTop(rect, dragAmount, minSizePx)
                return Rect(rect.left, newTop, newRight, rect.bottom)
            }
        }

        data object BottomLeft : Corner {
            override val alignment = Alignment.BottomStart

            override fun processResizeDrag(
                rect: Rect,
                dragAmount: Offset,
                minSizePx: Float,
                maxWidth: Float,
                maxHeight: Float,
            ): Rect {
                val newLeft = newLeft(rect, dragAmount, minSizePx)
                val newBottom = newBottom(rect, dragAmount, minSizePx, maxHeight)
                return Rect(newLeft, rect.top, rect.right, newBottom)
            }
        }

        data object BottomRight : Corner {
            override val alignment = Alignment.BottomEnd

            override fun processResizeDrag(
                rect: Rect,
                dragAmount: Offset,
                minSizePx: Float,
                maxWidth: Float,
                maxHeight: Float,
            ): Rect {
                val newRight = newRight(rect, dragAmount, minSizePx, maxWidth)
                val newBottom = newBottom(rect, dragAmount, minSizePx, maxHeight)
                return Rect(rect.left, rect.top, newRight, newBottom)
            }
        }
    }

    /** An edge of the rectangle. */
    sealed interface Edge : ResizeZone {
        data object Left : Edge {
            override fun processResizeDrag(
                rect: Rect,
                dragAmount: Offset,
                minSizePx: Float,
                maxWidth: Float,
                maxHeight: Float,
            ): Rect {
                val newLeft = newLeft(rect, dragAmount, minSizePx)
                return Rect(newLeft, rect.top, rect.right, rect.bottom)
            }
        }

        data object Top : Edge {
            override fun processResizeDrag(
                rect: Rect,
                dragAmount: Offset,
                minSizePx: Float,
                maxWidth: Float,
                maxHeight: Float,
            ): Rect {
                val newTop = newTop(rect, dragAmount, minSizePx)
                return Rect(rect.left, newTop, rect.right, rect.bottom)
            }
        }

        data object Right : Edge {
            override fun processResizeDrag(
                rect: Rect,
                dragAmount: Offset,
                minSizePx: Float,
                maxWidth: Float,
                maxHeight: Float,
            ): Rect {
                val newRight = newRight(rect, dragAmount, minSizePx, maxWidth)
                return Rect(rect.left, rect.top, newRight, rect.bottom)
            }
        }

        data object Bottom : Edge {
            override fun processResizeDrag(
                rect: Rect,
                dragAmount: Offset,
                minSizePx: Float,
                maxWidth: Float,
                maxHeight: Float,
            ): Rect {
                val newBottom = newBottom(rect, dragAmount, minSizePx, maxHeight)
                return Rect(rect.left, rect.top, rect.right, newBottom)
            }
        }
    }
}