Skip to content
This repository was archived by the owner on Jul 21, 2025. It is now read-only.

Commit 416c7a9

Browse files
committed
wip
1 parent a427fb0 commit 416c7a9

File tree

6 files changed

+488
-330
lines changed

6 files changed

+488
-330
lines changed

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ kotlin = "2.0.21"
1414
binaryCompatibilityValidator = "0.16.3"
1515
#compose
1616
composeMultiplatform = "1.7.1"
17+
colorpicker="1.1.2"
1718
#other
1819
dokka = "1.9.20"
1920
mavenPublish = "0.30.0"
@@ -22,6 +23,7 @@ mavenPublish = "0.30.0"
2223
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidxAppCompat" }
2324
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidxActivity" }
2425
androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "androidxAnnotation" }
26+
colorPicker = { module = "com.github.skydoves:colorpicker-compose", version.ref = "colorpicker" }
2527
[plugins]
2628
#android
2729
android-applcation = { id = "com.android.application", version.ref = "androidGradlePlugin" }

sample/compose-app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ kotlin {
6363
implementation(compose.material3)
6464
implementation(compose.components.uiToolingPreview)
6565
implementation(project(":compose-shadow"))
66+
implementation(libs.colorPicker)
6667
}
6768

6869
desktopMain.dependencies {

sample/compose-app/src/androidMain/AndroidManifest.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
77

88
<application
9-
android:name="com.adamglin.composeshadow.android.SampleApplication"
9+
android:name="com.admaglin.composeshadow.app.SampleApplication"
1010
android:allowBackup="true"
1111
android:enableOnBackInvokedCallback="true"
1212
android:hardwareAccelerated="true"
@@ -18,7 +18,7 @@
1818
android:usesCleartextTraffic="true"
1919
tools:targetApi="31">
2020
<activity
21-
android:name="com.adamglin.composeshadow.android.MainActivity"
21+
android:name="com.admaglin.composeshadow.app.MainActivity"
2222
android:exported="true"
2323
android:label="@string/title_activity_main"
2424
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import androidx.compose.animation.core.*
2+
import androidx.compose.foundation.ExperimentalFoundationApi
3+
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.border
5+
import androidx.compose.foundation.gestures.draggable2D
6+
import androidx.compose.foundation.gestures.rememberDraggable2DState
7+
import androidx.compose.foundation.layout.*
8+
import androidx.compose.foundation.shape.CircleShape
9+
import androidx.compose.runtime.Composable
10+
import androidx.compose.runtime.remember
11+
import androidx.compose.runtime.rememberCoroutineScope
12+
import androidx.compose.runtime.rememberUpdatedState
13+
import androidx.compose.ui.Modifier
14+
import androidx.compose.ui.draw.clip
15+
import androidx.compose.ui.draw.drawBehind
16+
import androidx.compose.ui.geometry.Offset
17+
import androidx.compose.ui.geometry.Size
18+
import androidx.compose.ui.graphics.Color
19+
import androidx.compose.ui.input.pointer.PointerIcon
20+
import androidx.compose.ui.input.pointer.pointerHoverIcon
21+
import androidx.compose.ui.platform.LocalDensity
22+
import androidx.compose.ui.unit.DpOffset
23+
import androidx.compose.ui.unit.dp
24+
import kotlinx.coroutines.launch
25+
26+
27+
@Composable
28+
fun rememberOffsetIndicatorState(
29+
onValueChange: (DpOffset) -> Unit,
30+
): OffsetIndicatorState {
31+
val onValueChangeState = rememberUpdatedState(onValueChange)
32+
return remember {
33+
OffsetIndicatorState(
34+
onValueChange = { onValueChangeState.value.invoke(it) }
35+
)
36+
}
37+
}
38+
39+
class OffsetIndicatorState(
40+
val onValueChange: (DpOffset) -> Unit,
41+
) {
42+
private val animatable = Animatable(DpOffset.Zero, DpOffset.VectorConverter)
43+
val targetValue get() = animatable.targetValue
44+
45+
46+
suspend fun snapTo(offset: DpOffset) {
47+
animatable.snapTo(offset)
48+
onValueChange.invoke(offset)
49+
}
50+
51+
suspend fun animateTo(offset: DpOffset) {
52+
onValueChange.invoke(offset)
53+
animatable.animateTo(offset, spring(stiffness = Spring.StiffnessHigh))
54+
}
55+
}
56+
57+
private val DpOffset.VectorConverter: TwoWayConverter<Offset, AnimationVector2D>
58+
get() = TwoWayConverter(
59+
convertToVector = { AnimationVector2D(it.x, it.y) },
60+
convertFromVector = { Offset(it.v1, it.v2) }
61+
)
62+
63+
64+
@OptIn(ExperimentalFoundationApi::class)
65+
@Composable
66+
fun OffsetIndicator(
67+
state: OffsetIndicatorState,
68+
modifier: Modifier = Modifier
69+
) {
70+
val density = LocalDensity.current
71+
val coroutineScope = rememberCoroutineScope()
72+
Box(modifier) {
73+
BoxWithConstraints(
74+
modifier = Modifier.fillMaxSize()
75+
.border(1.dp, color = OFFSET_INDICATOR_LINE_COLOR)
76+
.drawBehind {
77+
drawLine(
78+
OFFSET_INDICATOR_LINE_COLOR,
79+
strokeWidth = .5f,
80+
start = size.TopCenterOffset,
81+
end = size.BottomCenterOffset
82+
)
83+
drawLine(
84+
OFFSET_INDICATOR_LINE_COLOR,
85+
strokeWidth = .5f,
86+
start = size.CenterLeftOffset,
87+
end = size.CenterRightOffset,
88+
)
89+
}
90+
) {
91+
val boxWidth = maxWidth.value.dp
92+
93+
val realOffset =
94+
DpOffset(
95+
state.targetValue.x + boxWidth / 2 - MOVABLE_POINTER_SIZE / 2,
96+
state.targetValue.y + boxWidth / 2 - MOVABLE_POINTER_SIZE / 2
97+
)
98+
val draggable2DState = rememberDraggable2DState {
99+
with(density) {
100+
DpOffset(
101+
(state.targetValue.x + it.x.toDp()).coerceIn(-boxWidth / 2, boxWidth / 2),
102+
(state.targetValue.y + it.y.toDp()).coerceIn(-boxWidth / 2, boxWidth / 2),
103+
).let {
104+
println("${state.targetValue}result $it")
105+
coroutineScope.launch { state.snapTo(it) }
106+
}
107+
}
108+
}
109+
Box(
110+
modifier = Modifier
111+
.offset(realOffset.x, realOffset.y)
112+
.size(MOVABLE_POINTER_SIZE)
113+
.clip(CircleShape)
114+
.pointerHoverIcon(PointerIcon.Hand)
115+
.background(Color.Blue)
116+
.draggable2D(draggable2DState)
117+
)
118+
}
119+
}
120+
}
121+
122+
private val OFFSET_INDICATOR_LINE_COLOR = Color.LightGray
123+
private val MOVABLE_POINTER_SIZE = 10.dp
124+
125+
private val Size.TopLeftOffset: Offset
126+
get() = Offset.Zero
127+
128+
private val Size.TopCenterOffset: Offset
129+
get() = Offset(width / 2, 0f)
130+
131+
private val Size.TopRightOffset: Offset
132+
get() = Offset(width, 0f)
133+
134+
private val Size.CenterLeftOffset: Offset
135+
get() = Offset(0f, height / 2)
136+
137+
private val Size.CenterOffset: Offset
138+
get() = Offset(width / 2, height / 2)
139+
140+
private val Size.CenterRightOffset: Offset
141+
get() = Offset(width, height / 2)
142+
143+
private val Size.BottomLeftOffset: Offset
144+
get() = Offset(0f, height)
145+
146+
private val Size.BottomCenterOffset: Offset
147+
get() = Offset(width / 2, height)
148+
149+
private val Size.BottomRightOffset: Offset
150+
get() = Offset(width, height)

0 commit comments

Comments
 (0)