Skip to content

Commit e02f112

Browse files
add MorphLayout
1 parent 51dbe2b commit e02f112

File tree

1 file changed

+199
-0
lines changed
  • image/src/main/java/com/smarttoolfactory/image/transform

1 file changed

+199
-0
lines changed
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
package com.smarttoolfactory.image.transform
2+
3+
import androidx.compose.foundation.border
4+
import androidx.compose.foundation.layout.Box
5+
import androidx.compose.foundation.layout.fillMaxSize
6+
import androidx.compose.foundation.layout.padding
7+
import androidx.compose.runtime.*
8+
import androidx.compose.ui.Alignment
9+
import androidx.compose.ui.Modifier
10+
import androidx.compose.ui.draw.clipToBounds
11+
import androidx.compose.ui.geometry.Offset
12+
import androidx.compose.ui.geometry.Rect
13+
import androidx.compose.ui.geometry.Size
14+
import androidx.compose.ui.graphics.Color
15+
import androidx.compose.ui.graphics.graphicsLayer
16+
import androidx.compose.ui.input.pointer.pointerInput
17+
import androidx.compose.ui.layout.onGloballyPositioned
18+
import androidx.compose.ui.platform.LocalDensity
19+
import androidx.compose.ui.unit.Dp
20+
import androidx.compose.ui.unit.DpSize
21+
import androidx.compose.ui.unit.IntSize
22+
import androidx.compose.ui.unit.dp
23+
import com.smarttoolfactory.gesture.PointerRequisite
24+
import com.smarttoolfactory.gesture.detectPointerTransformGestures
25+
26+
@Composable
27+
fun MorphLayout(
28+
modifier: Modifier = Modifier,
29+
enabled: Boolean = true,
30+
handleRadius: Dp = 15.dp,
31+
handlePlacement: HandlePlacement = HandlePlacement.Corner,
32+
onDown: () -> Unit = {},
33+
onMove: (DpSize) -> Unit = {},
34+
onUp: () -> Unit = {},
35+
content: @Composable () -> Unit
36+
) {
37+
MorphSubcomposeLayout(
38+
modifier = Modifier.border(4.dp, Color.Red),
39+
handleRadius = handleRadius,
40+
updatePhysicalSize = false,
41+
mainContent = {
42+
Box(
43+
modifier = modifier.border(2.dp, Color.Green).onGloballyPositioned { },
44+
contentAlignment = Alignment.Center
45+
) {
46+
content()
47+
}
48+
},
49+
dependentContent = { intSize: IntSize ->
50+
51+
val dpSize = with(LocalDensity.current) {
52+
val rawWidth = intSize.width.toDp()
53+
val rawHeight = intSize.height.toDp()
54+
DpSize(rawWidth, rawHeight)
55+
}
56+
57+
MorphLayout(
58+
handleRadius = handleRadius,
59+
enabled = enabled,
60+
dpSize = dpSize,
61+
handlePlacement = handlePlacement,
62+
onDown = onDown,
63+
onMove = onMove,
64+
onUp = onUp,
65+
content = content
66+
)
67+
}
68+
)
69+
}
70+
71+
@Composable
72+
private fun MorphLayout(
73+
enabled: Boolean = true,
74+
handleRadius: Dp,
75+
dpSize: DpSize,
76+
handlePlacement: HandlePlacement,
77+
onDown: () -> Unit = {},
78+
onMove: (DpSize) -> Unit = {},
79+
onUp: () -> Unit = {},
80+
content: @Composable () -> Unit
81+
) {
82+
83+
val touchRegionRadius: Float
84+
val minDimension: Float
85+
val size: Size
86+
87+
val initialSize = remember {
88+
DpSize(
89+
dpSize.width + handleRadius * 2,
90+
dpSize.height + handleRadius * 2
91+
)
92+
}
93+
94+
var updatedSize by remember {
95+
mutableStateOf(initialSize)
96+
}
97+
98+
with(LocalDensity.current) {
99+
touchRegionRadius = handleRadius.toPx()
100+
minDimension = (touchRegionRadius * 4)
101+
size = updatedSize.toSize()
102+
}
103+
104+
val rectDraw = remember(updatedSize) {
105+
Rect(offset = Offset.Zero, size = size)
106+
}
107+
108+
val editModifier = Modifier
109+
.morph(
110+
enabled = enabled,
111+
initialSize = initialSize,
112+
touchRegionRadius = touchRegionRadius,
113+
minDimension = minDimension,
114+
handlePlacement = handlePlacement,
115+
onDown = onDown,
116+
onMove = { dpSizeChange: DpSize ->
117+
updatedSize = dpSizeChange
118+
onMove(updatedSize)
119+
},
120+
onUp = onUp
121+
)
122+
123+
var zoom by remember { mutableStateOf(1f) }
124+
var offset by remember { mutableStateOf(Offset.Zero) }
125+
126+
val transformModifier = Modifier
127+
.padding(handleRadius)
128+
.fillMaxSize()
129+
.clipToBounds()
130+
.graphicsLayer {
131+
translationX = offset.x
132+
translationY = offset.y
133+
scaleX = zoom
134+
scaleY = zoom
135+
}
136+
.pointerInput(Unit) {
137+
detectPointerTransformGestures(
138+
requisite = PointerRequisite.GreaterThan,
139+
numberOfPointers = 1,
140+
onGesture = { _,
141+
gesturePan: Offset,
142+
gestureZoom: Float,
143+
_,
144+
_,
145+
_ ->
146+
val newScale = (zoom * gestureZoom).coerceIn(1f, 3f)
147+
val newOffset = offset + gesturePan
148+
zoom = newScale
149+
150+
val maxX = (size.width * (zoom - 1) / 2f)
151+
val maxY = (size.height * (zoom - 1) / 2f)
152+
153+
offset = Offset(
154+
newOffset.x.coerceIn(-maxX, maxX),
155+
newOffset.y.coerceIn(-maxY, maxY)
156+
)
157+
}
158+
)
159+
}
160+
161+
ResizeImpl(
162+
modifier = editModifier,
163+
transformModifier = transformModifier,
164+
touchRegionRadius = touchRegionRadius,
165+
rectDraw = rectDraw,
166+
handlePlacement = handlePlacement,
167+
content = content
168+
)
169+
}
170+
171+
@Composable
172+
private fun ResizeImpl(
173+
modifier: Modifier,
174+
transformModifier: Modifier,
175+
touchRegionRadius: Float,
176+
rectDraw: Rect,
177+
handlePlacement: HandlePlacement,
178+
content: @Composable () -> Unit
179+
) {
180+
Box(
181+
modifier = modifier,
182+
contentAlignment = Alignment.Center
183+
) {
184+
185+
Box(
186+
modifier = transformModifier,
187+
contentAlignment = Alignment.Center
188+
) {
189+
content()
190+
}
191+
192+
HandleOverlay(
193+
modifier = Modifier.fillMaxSize(),
194+
radius = touchRegionRadius,
195+
rectDraw = rectDraw,
196+
handlePlacement = handlePlacement
197+
)
198+
}
199+
}

0 commit comments

Comments
 (0)