AmrJyniat
04/10/2025, 3:18 PMAmrJyniat
04/10/2025, 3:21 PMBox(
modifier = Modifier
.absolutePosition(x = offsetX, y = offsetY)
.requiredSize(
DpSize(
abs(width.value).dp,
abs(height.value).dp,
),
) // this affects offset if reached max size constraint
.graphicsLayer {
// rotate around center
transformOrigin = TransformOrigin.Center
rotationZ = state.rotation
},
) {
val lineColor = MaterialTheme.colorScheme.customColors.primary
val sideGrabSize = DpSize(10.dp, 30.dp)
Canvas(modifier = Modifier.fillMaxSize()) {
drawLine(
color = lineColor,
start = Offset(0f, 0f),
end = Offset((width).toPx(), 0f),
strokeWidth = 1.dp.toPx(),
)
drawLine(
color = lineColor,
start = Offset(0f, (height).toPx()),
end = Offset((width).toPx(), (height).toPx()),
strokeWidth = 1.dp.toPx(),
)
drawLine(
color = lineColor,
start = Offset(0f, 0f),
end = Offset(0f, (height).toPx()),
strokeWidth = 1.dp.toPx(),
)
drawLine(
color = lineColor,
start = Offset((width).toPx(), 0f),
end = Offset((width).toPx(), (height).toPx()),
strokeWidth = 1.dp.toPx(),
)
// Draw corner handles
if (cornerGrabsEnabled) {
val topRight = Offset(bottomRight.x, topLeft.y)
val bottomLeft = Offset(topLeft.x, bottomRight.y)
if (isCornersIntersects || state.isResizing){
val cornerPoint = when (state.lastCornerResized) {
Grab.TopLeft -> topLeft
Grab.TopRight -> topRight
Grab.BottomLeft -> bottomLeft
Grab.BottomRight -> bottomRight
else -> null
}
cornerPoint?.let {
drawShadowCircle(drawContext.canvas, center = it)
}
} else {
listOf(topLeft, topRight, bottomLeft, bottomRight).forEach {
drawShadowCircle(drawContext.canvas, center = it)
}
}
}
}
// ******************************************************************************
// # CORNER GRABS
// ******************************************************************************
if (cornerGrabsEnabled) {
val hotAreaSizePx = with(density) { hotAreaSize.toPx() }
val halfHotAreaSize = hotAreaSizePx.div(2)
val topLeftNew = Offset(topLeft.x - halfHotAreaSize, topLeft.y - halfHotAreaSize)
val bottomRightNew = Offset(bottomRight.x - halfHotAreaSize, bottomRight.y - halfHotAreaSize)
val topRightNew = Offset(bottomRight.x - halfHotAreaSize, topLeft.y - halfHotAreaSize)
val bottomLeftNew = Offset(topLeft.x - halfHotAreaSize, bottomRight.y - halfHotAreaSize)
// Check if any hot area intersects with the other
isCornersIntersects = checkPointsIntersect(
listOf(topLeftNew, bottomRightNew, topRightNew, bottomLeftNew),
hotAreaSizePx
)
// Top-left corner grab hot area
val topLeftCorner = if (isRtl) Grab.TopRight else Grab.TopLeft
GrabHotArea(
center = topLeftNew,
hotAreaSize = hotAreaSize,
grab = topLeftCorner,
resizingGrab = state.lastCornerResized,
isResizing = state.isResizing,
canvasScale = canvasScale,
isHotAreaEnabled = !isCornersIntersects || state.lastCornerResized == topLeftCorner,
onEvent = onEvent,
)
// Bottom-right corner grab
val bottomLeftCorner = if (isRtl) Grab.BottomLeft else Grab.BottomRight
GrabHotArea(
center = bottomRightNew,
hotAreaSize = hotAreaSize,
grab = bottomLeftCorner,
resizingGrab = state.lastCornerResized,
canvasScale = canvasScale,
isResizing = state.isResizing,
isHotAreaEnabled = !isCornersIntersects || state.lastCornerResized == bottomLeftCorner,
onEvent = onEvent,
)
// Top-right corner grab
val topRightCorner = if (isRtl) Grab.TopLeft else Grab.TopRight
GrabHotArea(
center = topRightNew,
hotAreaSize = hotAreaSize,
grab = topRightCorner,
resizingGrab = state.lastCornerResized,
canvasScale = canvasScale,
isResizing = state.isResizing,
isHotAreaEnabled = !isCornersIntersects || state.lastCornerResized == topRightCorner,
onEvent = onEvent,
)
// Bottom-Left corner grab
val bottomRightCorner = if (isRtl) Grab.BottomRight else Grab.BottomLeft
GrabHotArea(
center = bottomLeftNew,
hotAreaSize = hotAreaSize,
grab = bottomRightCorner,
resizingGrab = state.lastCornerResized,
canvasScale = canvasScale,
isResizing = state.isResizing,
isHotAreaEnabled = !isCornersIntersects || state.lastCornerResized == bottomRightCorner,
onEvent = onEvent,
)
}
}
This composable is responsible for showing resizing circle around the corner:
@Composable
private fun GrabHotArea(
center: Offset, // in pixels
hotAreaSize: Dp,
grab: Grab,
resizingGrab: Grab?,
canvasScale: Float,
isResizing: Boolean = false,
isHotAreaEnabled: Boolean = true,
onEvent: (CanvasEvent) -> Unit,
) {
if (!isHotAreaEnabled) return
val showCircle = grab.isCornerGrab && grab == resizingGrab && isResizing
val scale by animateFloatAsState(
targetValue = if (showCircle) 1f else 0f,
animationSpec = tween(durationMillis = 250),
label = "circleScale"
)
Box(
modifier = Modifier
.wrapContentSize(unbounded = true)
.size(hotAreaSize)
.offset {
center.toIntOffset()
}
.pointerInput(canvasScale) {
detectDragGestures(
onDrag = { _, dragAmount ->
onEvent(
CanvasEvent.SelectionViewGrabDragged(
grab = grab,
dragAmount = dragAmount.div(density).div(canvasScale),
),
)
},
onDragEnd = {
onEvent(
CanvasEvent.SelectionViewGrabDragEnded(
grab = grab,
),
)
}
)
},
){
if (scale > 0f) {
Box(
modifier = Modifier
.matchParentSize()
.graphicsLayer {
scaleX = scale
scaleY = scale
alpha = scale
}
.background(
color = CustomColors.primary.copy(alpha = 0.5f),
shape = CircleShape
)
)
}
}
}