Skip to content

Commit 51dbe2b

Browse files
add morph modifier
1 parent 681774f commit 51dbe2b

File tree

1 file changed

+323
-0
lines changed

1 file changed

+323
-0
lines changed
Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
package com.smarttoolfactory.image.transform
2+
3+
import androidx.compose.foundation.layout.size
4+
import androidx.compose.runtime.getValue
5+
import androidx.compose.runtime.mutableStateOf
6+
import androidx.compose.runtime.remember
7+
import androidx.compose.runtime.setValue
8+
import androidx.compose.ui.Modifier
9+
import androidx.compose.ui.composed
10+
import androidx.compose.ui.geometry.Offset
11+
import androidx.compose.ui.geometry.Rect
12+
import androidx.compose.ui.graphics.graphicsLayer
13+
import androidx.compose.ui.input.pointer.positionChange
14+
import androidx.compose.ui.platform.LocalDensity
15+
import androidx.compose.ui.unit.Density
16+
import androidx.compose.ui.unit.DpSize
17+
import com.smarttoolfactory.gesture.pointerMotionEvents
18+
19+
internal fun Modifier.morph(
20+
enabled: Boolean,
21+
initialSize: DpSize,
22+
touchRegionRadius: Float,
23+
minDimension: Float,
24+
handlePlacement: HandlePlacement,
25+
onDown: ()->Unit,
26+
onMove: (DpSize) -> Unit,
27+
onUp: () ->Unit
28+
) = composed(
29+
factory = {
30+
31+
val density = LocalDensity.current
32+
33+
var updatedSize by remember {
34+
mutableStateOf(initialSize)
35+
}
36+
37+
var transform by remember {
38+
mutableStateOf(
39+
Transform(rotation = 0f)
40+
)
41+
}
42+
43+
var touchRegion by remember(enabled) { mutableStateOf(TouchRegion.None) }
44+
45+
Modifier
46+
.size(updatedSize)
47+
.graphicsLayer {
48+
translationX = transform.translationX
49+
translationY = transform.translationY
50+
rotationZ = transform.rotation
51+
}
52+
.pointerMotionEvents(Unit,
53+
onDown = { change ->
54+
55+
val size = with(density) { updatedSize.toSize() }
56+
val position = change.position
57+
58+
touchRegion = getTouchRegion(
59+
position = position,
60+
rect = Rect(offset = Offset.Zero, size = size),
61+
threshold = touchRegionRadius * 2,
62+
handlePlacement = handlePlacement
63+
)
64+
65+
onDown()
66+
67+
},
68+
onMove = { change ->
69+
val dragAmount = change.positionChange()
70+
71+
updateTransform(
72+
touchRegion,
73+
density,
74+
updatedSize,
75+
dragAmount,
76+
minDimension,
77+
transform,
78+
onUpdate = { dpSize, transformChange ->
79+
updatedSize = dpSize
80+
onMove(dpSize)
81+
transform = transformChange
82+
}
83+
)
84+
85+
change.consume()
86+
},
87+
onUp = {
88+
touchRegion = TouchRegion.None
89+
onUp()
90+
}
91+
)
92+
},
93+
inspectorInfo = {
94+
name = "morph"
95+
// add name and value of each argument
96+
properties["enabled"] = enabled
97+
properties["initialSize"] = initialSize
98+
properties["touchRegionRadius"] = touchRegionRadius
99+
properties["minDimension"] = minDimension
100+
properties["handlePlacement"] = handlePlacement
101+
properties["onDown"] = onDown
102+
properties["onMove"] = onMove
103+
properties["onUp"] = onUp
104+
}
105+
)
106+
107+
internal fun updateTransform(
108+
touchRegion: TouchRegion,
109+
density: Density,
110+
updatedSize: DpSize,
111+
dragAmount: Offset,
112+
minDimension: Float,
113+
transform: Transform,
114+
onUpdate: (DpSize, Transform) -> Unit
115+
) {
116+
117+
val translationX: Float
118+
val translationY: Float
119+
val newSize: DpSize
120+
121+
when (touchRegion) {
122+
123+
// Corners
124+
TouchRegion.TopLeft -> {
125+
126+
with(density) {
127+
128+
// Get current dimensions of Composable
129+
val oldWidth = updatedSize.width.toPx()
130+
val oldHeight = updatedSize.height.toPx()
131+
132+
// Change dimensions of composable by drag amount limited to
133+
// minimum dimension constraint
134+
val width = (oldWidth - dragAmount.x).coerceAtLeast(minDimension)
135+
val height = (oldHeight - dragAmount.y).coerceAtLeast(minDimension)
136+
137+
// Translate Composable as the difference between old and new dimensions
138+
translationX = transform.translationX + oldWidth - width
139+
translationY = transform.translationY + oldHeight - height
140+
newSize = DpSize(width.toDp(), height.toDp())
141+
}
142+
143+
onUpdate(
144+
newSize,
145+
transform.copy(
146+
translationX = translationX,
147+
translationY = translationY,
148+
)
149+
)
150+
}
151+
152+
TouchRegion.BottomLeft -> {
153+
154+
with(density) {
155+
156+
// Get current dimensions of Composable
157+
val oldWidth = updatedSize.width.toPx()
158+
val oldHeight = updatedSize.height.toPx()
159+
160+
// Change dimensions of composable by drag amount limited to
161+
// minimum dimension constraint
162+
val width = (oldWidth - dragAmount.x).coerceAtLeast(minDimension)
163+
val height = (oldHeight + dragAmount.y).coerceAtLeast(minDimension)
164+
165+
translationX = transform.translationX + oldWidth - width
166+
translationY = transform.translationY
167+
168+
// Translate Composable as the difference between old and new dimensions
169+
newSize = DpSize(width.toDp(), height.toDp())
170+
}
171+
172+
onUpdate(
173+
newSize,
174+
transform.copy(
175+
translationX = translationX,
176+
translationY = translationY,
177+
)
178+
)
179+
}
180+
181+
TouchRegion.TopRight -> {
182+
183+
with(density) {
184+
185+
// Get current dimensions of Composable
186+
val oldWidth = updatedSize.width.toPx()
187+
val oldHeight = updatedSize.height.toPx()
188+
189+
val width = (oldWidth + dragAmount.x).coerceAtLeast(minDimension)
190+
val height = (oldHeight - dragAmount.y).coerceAtLeast(minDimension)
191+
192+
translationX = transform.translationX
193+
translationY = transform.translationY + oldHeight - height
194+
195+
newSize = DpSize(width.toDp(), height.toDp())
196+
}
197+
198+
onUpdate(
199+
newSize,
200+
transform.copy(
201+
translationX = translationX,
202+
translationY = translationY,
203+
)
204+
)
205+
}
206+
207+
TouchRegion.BottomRight -> {
208+
with(density) {
209+
210+
// Get current dimensions of Composable
211+
val oldWidth = updatedSize.width.toPx()
212+
val oldHeight = updatedSize.height.toPx()
213+
214+
val width = (oldWidth + dragAmount.x).coerceAtLeast(minDimension)
215+
val height = (oldHeight + dragAmount.y).coerceAtLeast(minDimension)
216+
217+
translationX = transform.translationX
218+
translationY = transform.translationY
219+
220+
newSize = DpSize(width.toDp(), height.toDp())
221+
}
222+
223+
onUpdate(
224+
newSize,
225+
transform.copy(
226+
translationX = translationX,
227+
translationY = translationY,
228+
)
229+
)
230+
}
231+
232+
// Sides
233+
TouchRegion.CenterLeft -> {
234+
235+
with(density) {
236+
237+
// Get current width of Composable
238+
val oldWidth = updatedSize.width.toPx()
239+
val width = (oldWidth - dragAmount.x).coerceAtLeast(minDimension)
240+
241+
translationX = transform.translationX + oldWidth - width
242+
newSize = updatedSize.copy(width = width.toDp())
243+
}
244+
245+
onUpdate(
246+
newSize,
247+
transform.copy(translationX = translationX)
248+
)
249+
}
250+
251+
TouchRegion.TopCenter -> {
252+
253+
with(density) {
254+
255+
// Get current height of Composable
256+
val oldHeight = updatedSize.height.toPx()
257+
258+
val height = (oldHeight - dragAmount.y).coerceAtLeast(minDimension)
259+
260+
translationY = transform.translationY + oldHeight - height
261+
newSize = updatedSize.copy(height = height.toDp())
262+
}
263+
264+
onUpdate(
265+
newSize,
266+
transform.copy(translationY = translationY)
267+
)
268+
}
269+
270+
TouchRegion.CenterRight -> {
271+
272+
with(density) {
273+
274+
// Get current width of Composable
275+
val oldWidth = updatedSize.width.toPx()
276+
277+
val width = (oldWidth + dragAmount.x).coerceAtLeast(minDimension)
278+
279+
translationX = transform.translationX
280+
newSize = updatedSize.copy(width = width.toDp())
281+
}
282+
283+
onUpdate(
284+
newSize,
285+
transform.copy(translationX = translationX)
286+
)
287+
}
288+
289+
TouchRegion.BottomCenter -> {
290+
291+
with(density) {
292+
293+
// Get current height of Composable
294+
val oldHeight = updatedSize.height.toPx()
295+
296+
val height = (oldHeight + dragAmount.y).coerceAtLeast(minDimension)
297+
298+
translationY = transform.translationY
299+
newSize = updatedSize.copy(height = height.toDp())
300+
}
301+
302+
onUpdate(
303+
newSize,
304+
transform.copy(translationY = translationY)
305+
)
306+
}
307+
308+
TouchRegion.Inside -> {
309+
310+
translationX = transform.translationX + dragAmount.x
311+
translationY = transform.translationY + dragAmount.y
312+
313+
onUpdate(
314+
updatedSize,
315+
transform.copy(
316+
translationX = translationX,
317+
translationY = translationY,
318+
)
319+
)
320+
}
321+
else -> Unit
322+
}
323+
}

0 commit comments

Comments
 (0)