11package com.singularityuniverse.lib.userguide
22
3+ import androidx.compose.foundation.background
34import androidx.compose.foundation.border
45import androidx.compose.foundation.layout.*
56import androidx.compose.material3.Text
67import androidx.compose.runtime.Composable
8+ import androidx.compose.runtime.mutableStateOf
9+ import androidx.compose.runtime.remember
710import androidx.compose.ui.Alignment
811import androidx.compose.ui.Modifier
912import androidx.compose.ui.geometry.Rect
1013import androidx.compose.ui.graphics.Color
14+ import androidx.compose.ui.layout.onSizeChanged
1115import androidx.compose.ui.platform.LocalDensity
1216import androidx.compose.ui.unit.Density
1317import androidx.compose.ui.unit.Dp
18+ import androidx.compose.ui.unit.IntOffset
19+ import androidx.compose.ui.unit.IntSize
20+ import androidx.compose.ui.unit.center
1421import androidx.compose.ui.unit.dp
1522import androidx.compose.ui.zIndex
1623import com.singularityuniverse.lib.userguide.tools.LocalIsDebugModeEnabled
@@ -29,24 +36,28 @@ fun UserGuideOverlay(
2936 .apply { tooltipBuilder(this , currentTarget ? : return ) }
3037
3138 if (state.isShowing && state.animationState == GuideAnimationState .NAVIGATING ) {
32- Column {
39+ Box (
40+ modifier = Modifier .fillMaxSize()
41+ ) {
42+ // top section container
3343 Box (
3444 modifier = Modifier
3545 .fillMaxWidth()
36- .height((scope.targetRect .top / density.density).dp)
46+ .height((scope.highlightRect .top / density.density).dp)
3747 .border(borderStroke1Dp(if (isDebugMode) Color .Red else Color .Transparent )),
3848 contentAlignment = when {
3949 scope.isTargetHorizontallyCentered -> Alignment .BottomCenter
4050 scope.isPreferLeft -> Alignment .BottomEnd
4151 else -> Alignment .BottomStart
4252 }
4353 ) {
44- scope.aboveContent?. invoke(this )
54+ scope.aboveContents.map { it. invoke(this ) }
4555 }
4656
57+ // top section container
4758 Box (
4859 modifier = Modifier
49- .padding(top = (scope.targetRect.height / density.density).dp)
60+ .padding(top = (scope.highlightRect.bottom / density.density).dp)
5061 .fillMaxWidth()
5162 .fillMaxHeight()
5263 .border(borderStroke1Dp(if (isDebugMode) Color .Red else Color .Transparent )),
@@ -58,7 +69,7 @@ fun UserGuideOverlay(
5869 }
5970 ) {
6071
61- scope.bellowContent?. invoke(this )
72+ scope.bellowContents.map { it. invoke(this ) }
6273
6374 if (isDebugMode)
6475 Text (
@@ -73,6 +84,8 @@ fun UserGuideOverlay(
7384 .zIndex(1f )
7485 )
7586 }
87+
88+ scope.topContents.map { it.invoke(this ) }
7689 }
7790 }
7891}
@@ -86,31 +99,103 @@ class UserGuideOverlayScope(
8699 }
87100
88101 val rootRect: Rect get() = state.fullScreenRect
89- val targetRect: Rect get() = state.animatedRect
102+ val highlightRect: Rect get() = state.animatedRect
103+ val targetRect: Rect ? get() = state.getCurrentTarget()?.rect
90104
91- val isPreferTop: Boolean get() = targetRect .center.y > rootRect.center.y
105+ val isPreferTop: Boolean get() = highlightRect .center.y > rootRect.center.y
92106 val isPreferBottom: Boolean get() = ! isPreferTop
93- val isPreferLeft: Boolean get() = targetRect .center.x < rootRect.center.x
107+ val isPreferLeft: Boolean get() = highlightRect .center.x < rootRect.center.x
94108 val isPreferRight: Boolean get() = ! isPreferLeft
95109
96110 val isTargetHorizontallyCentered: Boolean
97- get() = (targetRect .center.x - rootRect.center.x).absoluteValue < FLOAT_ROUNDING_PIXEL_TOLERANCE
98- val isTargetVerticallyCentered: Boolean = targetRect .center.y == rootRect.center.y
111+ get() = (highlightRect .center.x - rootRect.center.x).absoluteValue < FLOAT_ROUNDING_PIXEL_TOLERANCE
112+ val isTargetVerticallyCentered: Boolean = highlightRect .center.y == rootRect.center.y
99113
100- val targetLeftSpace: Dp = (targetRect .left / density.density).dp
101- val targetRightSpace: Dp = ((rootRect.width - targetRect .right) / density.density).dp
114+ val targetLeftSpace: Dp = (highlightRect .left / density.density).dp
115+ val targetRightSpace: Dp = ((rootRect.width - highlightRect .right) / density.density).dp
102116
103- internal var bellowContent : ( @Composable BoxScope .() -> Unit ) ? = null
117+ internal var bellowContents : List < @Composable BoxScope .() -> Unit > = emptyList()
104118 private set
105119
106120 fun drawBellow (content : @Composable BoxScope .() -> Unit ) {
107- bellowContent = content
121+ bellowContents + = content
108122 }
109123
110- internal var aboveContent : ( @Composable BoxScope .() -> Unit ) ? = null
124+ internal var aboveContents : List < @Composable BoxScope .() -> Unit > = emptyList()
111125 private set
112126
113127 fun drawAbove (content : @Composable BoxScope .() -> Unit ) {
114- aboveContent = content
128+ aboveContents + = content
129+ }
130+
131+ internal var topContents: List < @Composable BoxScope .() -> Unit > = emptyList()
132+ private set
133+
134+ fun drawOnTop (content : @Composable BoxScope .() -> Unit ) {
135+ topContents + = content
136+ }
137+
138+ fun drawPointer (content : @Composable BoxScope .() -> Unit ) {
139+ topContents + = @Composable {
140+ val pointerSize = remember { mutableStateOf(IntSize .Zero ) }
141+ val pointerCenter = pointerSize.value.center
142+
143+ Box (
144+ modifier = Modifier
145+ .onSizeChanged { pointerSize.value = it }
146+ // translate container into proper position automatically
147+ .offset {
148+ val targetCenter = targetRect?.center
149+ ?.let { IntOffset (it.x.toInt(), it.y.toInt()) }
150+ ? : IntOffset .Zero
151+
152+ val center = targetCenter - pointerCenter
153+
154+ val verticalOffsetCorrection = (if (isPreferTop) - 1 else 1 ) *
155+ (pointerSize.value.height + (targetRect?.height ? : 0f )) / 2
156+
157+ center + IntOffset (x = 0 , y = verticalOffsetCorrection.toInt())
158+ }
159+ ) {
160+ content()
161+ }
162+ }
163+ }
164+
165+ fun drawAuto (content : @Composable BoxScope .() -> Unit ) {
166+ if (isPreferTop) drawAbove {
167+ DrawAutoContainer {
168+ content()
169+ }
170+ }
171+
172+ if (isPreferBottom) drawBellow {
173+ DrawAutoContainer {
174+ content()
175+ }
176+ }
177+ }
178+
179+ @Composable
180+ private fun DrawAutoContainer (content : @Composable BoxScope .() -> Unit ) {
181+ val isDebugMode = LocalIsDebugModeEnabled .current
182+ Box (
183+ modifier = Modifier
184+ .padding(
185+ start = when {
186+ isTargetHorizontallyCentered -> 0 .dp
187+ isPreferLeft -> targetLeftSpace
188+ else -> 0 .dp
189+ },
190+ end = when {
191+ isTargetHorizontallyCentered -> 0 .dp
192+ isPreferRight -> targetRightSpace
193+ else -> 0 .dp
194+ },
195+ )
196+ .border(borderStroke1Dp(if (isDebugMode) Color .Blue else Color .Transparent )),
197+ ) {
198+ content()
199+ }
115200 }
116201}
0 commit comments