Skip to content

Commit b0c42af

Browse files
committed
feat(android, match-infos): drag and drop support
1 parent 2e31e8e commit b0c42af

File tree

5 files changed

+228
-66
lines changed

5 files changed

+228
-66
lines changed

.editorconfig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
indent_size = 4
7+
indent_style = space
8+
insert_final_newline = false
9+
max_line_length = 120
10+
tab_width = 4

core/shared-ui/src/androidMain/kotlin/com/neoutils/neoregex/core/sharedui/component/MatchResult.android.kt

Lines changed: 0 additions & 64 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
/*
2+
* NeoRegex.
3+
*
4+
* Copyright (C) 2024 Irineu A. Silva.
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
package com.neoutils.neoregex.core.sharedui.component
20+
21+
import androidx.compose.animation.AnimatedVisibility
22+
import androidx.compose.animation.core.Animatable
23+
import androidx.compose.animation.core.VectorConverter
24+
import androidx.compose.animation.fadeIn
25+
import androidx.compose.animation.fadeOut
26+
import androidx.compose.foundation.background
27+
import androidx.compose.foundation.border
28+
import androidx.compose.foundation.gestures.detectDragGestures
29+
import androidx.compose.foundation.layout.*
30+
import androidx.compose.foundation.shape.RoundedCornerShape
31+
import androidx.compose.material3.MaterialTheme.colorScheme
32+
import androidx.compose.material3.MaterialTheme.typography
33+
import androidx.compose.material3.Text
34+
import androidx.compose.runtime.*
35+
import androidx.compose.ui.Alignment
36+
import androidx.compose.ui.Modifier
37+
import androidx.compose.ui.geometry.Offset
38+
import androidx.compose.ui.geometry.Rect
39+
import androidx.compose.ui.input.pointer.pointerInput
40+
import androidx.compose.ui.layout.boundsInRoot
41+
import androidx.compose.ui.layout.onGloballyPositioned
42+
import androidx.compose.ui.platform.LocalDensity
43+
import androidx.compose.ui.unit.DpSize
44+
import androidx.compose.ui.unit.dp
45+
import androidx.compose.ui.unit.round
46+
import androidx.compose.ui.unit.toSize
47+
import com.neoutils.neoregex.core.designsystem.theme.NeoTheme.dimensions
48+
import com.neoutils.neoregex.core.designsystem.theme.NeoTheme.fontSizes
49+
import com.neoutils.neoregex.core.resources.Res
50+
import com.neoutils.neoregex.core.resources.match_result_infos
51+
import kotlinx.coroutines.launch
52+
import org.jetbrains.compose.resources.pluralStringResource
53+
import kotlin.time.Duration
54+
import kotlin.time.DurationUnit
55+
56+
@Composable
57+
actual fun BoxScope.MatchesInfos(
58+
duration: Duration,
59+
matches: Int,
60+
modifier: Modifier
61+
) {
62+
val density = LocalDensity.current
63+
64+
var isRunning by remember { mutableStateOf(false) }
65+
66+
val animateOffset = remember {
67+
Animatable(
68+
Offset.Zero,
69+
Offset.VectorConverter,
70+
)
71+
}
72+
73+
var targetSize by remember { mutableStateOf(DpSize.Zero) }
74+
75+
var targetRect by remember { mutableStateOf(Rect.Zero) }
76+
77+
var rectMap by remember { mutableStateOf<Map<Alignment, Rect>>(mapOf()) }
78+
79+
var currentAlignment by remember { mutableStateOf(Alignment.BottomEnd) }
80+
81+
val scope = rememberCoroutineScope()
82+
83+
listOf(
84+
Alignment.TopEnd,
85+
Alignment.BottomEnd
86+
).forEach { alignment ->
87+
AnimatedVisibility(
88+
visible = isRunning,
89+
enter = fadeIn(),
90+
exit = fadeOut(),
91+
modifier = Modifier
92+
.align(alignment)
93+
.padding(4.dp)
94+
) {
95+
Box(
96+
modifier = Modifier
97+
.background(
98+
color = colorScheme.primary.copy(
99+
alpha = 0.2f
100+
),
101+
shape = RoundedCornerShape(dimensions.tiny)
102+
)
103+
.run {
104+
rectMap[alignment]
105+
?.takeIf {
106+
!it.intersect(targetRect).isEmpty
107+
}
108+
?.let {
109+
border(
110+
width = 1.dp,
111+
color = colorScheme.primary,
112+
shape = RoundedCornerShape(dimensions.tiny)
113+
)
114+
} ?: this
115+
}
116+
.onGloballyPositioned {
117+
rectMap = rectMap + mapOf(
118+
alignment to it.boundsInRoot()
119+
)
120+
}
121+
.size(targetSize)
122+
)
123+
}
124+
}
125+
126+
Text(
127+
text = pluralStringResource(
128+
Res.plurals.match_result_infos,
129+
matches, matches,
130+
duration.toString(
131+
unit = DurationUnit.MILLISECONDS,
132+
decimals = 3
133+
)
134+
),
135+
fontSize = fontSizes.tiny,
136+
style = typography.labelSmall,
137+
modifier = Modifier
138+
.align(currentAlignment)
139+
.offset { animateOffset.value.round() }
140+
.padding(dimensions.tiny) // external
141+
.background(
142+
color = colorScheme.surfaceVariant,
143+
shape = RoundedCornerShape(dimensions.tiny)
144+
)
145+
.onGloballyPositioned {
146+
targetSize = density.run {
147+
it.size
148+
.toSize()
149+
.toDpSize()
150+
}
151+
152+
targetRect = it.boundsInRoot()
153+
}
154+
.pointerInput(Unit) {
155+
detectDragGestures(
156+
onDragStart = {
157+
isRunning = true
158+
},
159+
onDragEnd = {
160+
rectMap.entries
161+
.find {
162+
!it.value.intersect(targetRect).isEmpty
163+
}
164+
?.let {
165+
scope.launch {
166+
167+
currentAlignment = it.key
168+
169+
animateOffset.snapTo(
170+
targetValue = targetRect.topLeft - it.value.topLeft
171+
)
172+
173+
animateOffset.animateTo(Offset.Zero)
174+
}
175+
} ?: run {
176+
scope.launch {
177+
animateOffset.animateTo(Offset.Zero)
178+
}
179+
}
180+
181+
isRunning = false
182+
},
183+
onDrag = { change, dragAmount ->
184+
change.consume()
185+
186+
scope.launch {
187+
animateOffset.snapTo(
188+
targetValue = animateOffset.value + dragAmount
189+
)
190+
}
191+
},
192+
onDragCancel = {
193+
scope.launch {
194+
animateOffset.animateTo(Offset.Zero)
195+
}
196+
197+
isRunning = false
198+
}
199+
)
200+
}
201+
.padding(dimensions.micro) // internal
202+
)
203+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,13 @@ actual fun BoxScope.MatchesInfos(
186186
)
187187
}
188188
},
189+
onDragCancel = {
190+
scope.launch {
191+
animateOffset.animateTo(Offset.Zero)
192+
}
193+
194+
isRunning = false
195+
}
189196
)
190197
.padding(dimensions.micro) // internal
191198
)

feature/matcher/src/commonMain/kotlin/com/neoutils/neoregex/feature/matcher/MatcherScreen.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,11 @@ import androidx.compose.foundation.layout.*
2525
import androidx.compose.foundation.shape.CornerBasedShape
2626
import androidx.compose.foundation.shape.CornerSize
2727
import androidx.compose.foundation.shape.RoundedCornerShape
28-
import androidx.compose.material3.*
28+
import androidx.compose.material3.Icon
29+
import androidx.compose.material3.LocalContentColor
2930
import androidx.compose.material3.MaterialTheme.colorScheme
31+
import androidx.compose.material3.Surface
32+
import androidx.compose.material3.VerticalDivider
3033
import androidx.compose.runtime.Composable
3134
import androidx.compose.runtime.getValue
3235
import androidx.compose.ui.Alignment
@@ -45,7 +48,10 @@ import cafe.adriel.voyager.core.screen.Screen
4548
import com.neoutils.neoregex.core.common.util.Command
4649
import com.neoutils.neoregex.core.designsystem.textfield.NeoTextField
4750
import com.neoutils.neoregex.core.designsystem.theme.NeoTheme.dimensions
48-
import com.neoutils.neoregex.core.resources.*
51+
import com.neoutils.neoregex.core.resources.Res
52+
import com.neoutils.neoregex.core.resources.ic_redo_24
53+
import com.neoutils.neoregex.core.resources.ic_undo_24
54+
import com.neoutils.neoregex.core.resources.insert_regex_hint
4955
import com.neoutils.neoregex.core.sharedui.component.MatchesInfos
5056
import com.neoutils.neoregex.core.sharedui.component.TextEditor
5157
import com.neoutils.neoregex.feature.matcher.action.MatcherAction

0 commit comments

Comments
 (0)