From 609e9804a6e98539cd9328b28544ee9aaa6f74f1 Mon Sep 17 00:00:00 2001 From: "Irineu A. Silva" Date: Mon, 7 Oct 2024 06:39:52 -0300 Subject: [PATCH 1/3] fix(web): break line don't work with virtual keyboard #39 --- build-logic/src/main/kotlin/extension/Project.kt | 4 ++-- gradle/libs.versions.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build-logic/src/main/kotlin/extension/Project.kt b/build-logic/src/main/kotlin/extension/Project.kt index 5ab469df..66040a6f 100644 --- a/build-logic/src/main/kotlin/extension/Project.kt +++ b/build-logic/src/main/kotlin/extension/Project.kt @@ -27,8 +27,8 @@ val config = Config( version = Config.Version( major = 2, minor = 1, - patch = 2, - phase = Config.Phase.RELEASE + patch = 3, + phase = Config.Phase.DEVELOP ), android = Config.Android( compileSdk = 34, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e085ed29..8da44394 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] agp = "8.2.2" # Kotlin Plugin 1.9.22 support APG 7.0 ~ 8.2 -cmp = "1.6.11" # Jetpack Compose 1.6.7 +cmp = "1.7.0-rc01" # Jetpack Compose 1.7.0 kotlin = "2.0.10" voyager = "1.1.0-beta02" # 1.1.0+ is required for web support From 12613e115807097ea94d83b15fbe6a3aabf41262 Mon Sep 17 00:00:00 2001 From: "Irineu A. Silva" Date: Mon, 7 Oct 2024 07:23:00 -0300 Subject: [PATCH 2/3] fix(web): trigger match details from touch screen #40 --- .../sharedui/component/TextEditor.android.kt | 84 -------- .../core/sharedui/component/MatchDetails.kt | 126 ++++++++++++ .../core/sharedui/component/TextEditor.web.kt | 185 ++++++++++++++---- 3 files changed, 273 insertions(+), 122 deletions(-) create mode 100644 core/shared-ui/src/commonMain/kotlin/com/neo/regex/core/sharedui/component/MatchDetails.kt diff --git a/core/shared-ui/src/androidMain/kotlin/com/neo/regex/core/sharedui/component/TextEditor.android.kt b/core/shared-ui/src/androidMain/kotlin/com/neo/regex/core/sharedui/component/TextEditor.android.kt index 9a7d3222..c9103076 100644 --- a/core/shared-ui/src/androidMain/kotlin/com/neo/regex/core/sharedui/component/TextEditor.android.kt +++ b/core/shared-ui/src/androidMain/kotlin/com/neo/regex/core/sharedui/component/TextEditor.android.kt @@ -229,87 +229,3 @@ actual fun TextEditor( } } } - -@Composable -private fun MatchDetails( - match: Match, - modifier: Modifier = Modifier, - textStyle: TextStyle = typography.bodyLarge -) = Surface( - modifier = modifier, - shape = RectangleShape, - shadowElevation = dimensions.small -) { - Row( - horizontalArrangement = Arrangement.SpaceAround, - ) { - - Column( - modifier = Modifier - .padding(dimensions.default) - .weight(weight = 1f) - ) { - Text( - text = "match ${match.number}", - style = textStyle.copy( - fontWeight = FontWeight.Bold - ) - ) - - HorizontalDivider( - modifier = Modifier.padding( - vertical = dimensions.small, - ) - ) - - Text( - text = buildAnnotatedString { - withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { - append("range: ") - } - withStyle(SpanStyle(fontWeight = FontWeight.Normal)) { - append(match.range.toString()) - } - }, - style = textStyle - ) - } - - if (match.groups.isNotEmpty()) { - Column( - modifier = Modifier - .padding(dimensions.default) - .weight(weight = 1f) - ) { - Text( - text = "groups", - style = textStyle.copy( - fontWeight = FontWeight.Bold - ) - ) - - HorizontalDivider( - modifier = Modifier.padding( - vertical = dimensions.small, - ) - ) - - match.groups.forEachIndexed { index, group -> - Text( - text = buildAnnotatedString { - withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { - append("$index: ") - } - withStyle(SpanStyle(fontWeight = FontWeight.Normal)) { - append(group) - } - }, - style = textStyle, - overflow = TextOverflow.Ellipsis, - maxLines = 1 - ) - } - } - } - } -} diff --git a/core/shared-ui/src/commonMain/kotlin/com/neo/regex/core/sharedui/component/MatchDetails.kt b/core/shared-ui/src/commonMain/kotlin/com/neo/regex/core/sharedui/component/MatchDetails.kt new file mode 100644 index 00000000..6b6ad2da --- /dev/null +++ b/core/shared-ui/src/commonMain/kotlin/com/neo/regex/core/sharedui/component/MatchDetails.kt @@ -0,0 +1,126 @@ +/* + * NeoRegex. + * + * Copyright (C) 2024 Irineu A. Silva. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.neo.regex.core.sharedui.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.text.withStyle +import com.neo.regex.core.designsystem.theme.NeoTheme.dimensions +import com.neo.regex.core.sharedui.model.Match + +@Composable +fun MatchDetails( + match: Match, + modifier: Modifier = Modifier, + textStyle: TextStyle = TextStyle() +) = Surface( + modifier = modifier, + shape = RectangleShape, + shadowElevation = dimensions.small +) { + + val mergedTextStyle = typography.bodyLarge.merge(textStyle) + + Row( + horizontalArrangement = Arrangement.SpaceAround, + ) { + + Column( + modifier = Modifier + .padding(dimensions.default) + .weight(weight = 1f) + ) { + Text( + text = "match ${match.number}", + style = mergedTextStyle.copy( + fontWeight = FontWeight.Bold + ) + ) + + HorizontalDivider( + modifier = Modifier.padding( + vertical = dimensions.small, + ) + ) + + Text( + text = buildAnnotatedString { + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + append("range: ") + } + withStyle(SpanStyle(fontWeight = FontWeight.Normal)) { + append(match.range.toString()) + } + }, + style = mergedTextStyle + ) + } + + if (match.groups.isNotEmpty()) { + Column( + modifier = Modifier + .padding(dimensions.default) + .weight(weight = 1f) + ) { + Text( + text = "groups", + style = mergedTextStyle.copy( + fontWeight = FontWeight.Bold + ) + ) + + HorizontalDivider( + modifier = Modifier.padding( + vertical = dimensions.small, + ) + ) + + match.groups.forEachIndexed { index, group -> + Text( + text = buildAnnotatedString { + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + append("$index: ") + } + withStyle(SpanStyle(fontWeight = FontWeight.Normal)) { + append(group) + } + }, + style = mergedTextStyle, + overflow = TextOverflow.Ellipsis, + maxLines = 1 + ) + } + } + } + } +} diff --git a/core/shared-ui/src/webMain/kotlin/com/neo/regex/core/sharedui/component/TextEditor.web.kt b/core/shared-ui/src/webMain/kotlin/com/neo/regex/core/sharedui/component/TextEditor.web.kt index c7ef7dd3..8fb320f9 100644 --- a/core/shared-ui/src/webMain/kotlin/com/neo/regex/core/sharedui/component/TextEditor.web.kt +++ b/core/shared-ui/src/webMain/kotlin/com/neo/regex/core/sharedui/component/TextEditor.web.kt @@ -18,11 +18,15 @@ package com.neo.regex.core.sharedui.component +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedContentTransitionScope.SlideDirection +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.togetherWith import androidx.compose.foundation.* -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.PressInteraction +import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.BasicTextField import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography @@ -50,6 +54,7 @@ import com.neo.regex.core.sharedui.extension.toText import com.neo.regex.core.sharedui.extension.tooltip import com.neo.regex.core.sharedui.model.Match import com.neo.regex.core.sharedui.model.MatchBox +import kotlinx.browser.window @OptIn(ExperimentalComposeUiApi::class) @Composable @@ -72,10 +77,51 @@ actual fun TextEditor( var hoverOffset by remember { mutableStateOf(null) } + val interactionSource = remember { MutableInteractionSource() } + + var pressedMatchOffset by remember { mutableStateOf(null) } + + var selectedMatch by remember { mutableStateOf(null) } + val textMeasurer = rememberTextMeasurer() + val interactionMode = InteractionMode.Current + val colorScheme = colorScheme + if (interactionMode == InteractionMode.TOUCH) { + LaunchedEffect(interactionSource, matches) { + interactionSource.interactions.collect { interaction -> + when (interaction) { + is PressInteraction.Press -> { + pressedMatchOffset = interaction.pressPosition + } + + is PressInteraction.Release -> { + selectedMatch = textLayout?.let { textLayout -> + matches.firstOrNull { match -> + textLayout + .getBoundingBoxes( + match.range.first, + match.range.last + ) + .any { + it.contains(interaction.press.pressPosition) + } + } + } + + pressedMatchOffset = null + } + + is PressInteraction.Cancel -> { + pressedMatchOffset = null + } + } + } + } + } + Row(modifier) { LineNumbers( @@ -108,6 +154,7 @@ actual fun TextEditor( ), color = colorScheme.onSurface, ), + interactionSource = interactionSource, cursorBrush = SolidColor(colorScheme.onSurface), modifier = Modifier .background(colorScheme.surface) @@ -155,43 +202,71 @@ actual fun TextEditor( drawContent() - hoverOffset?.let { offset -> - val matchBox = matchBoxes.firstOrNull { (_, rect) -> - rect.contains(offset) - } + when (interactionMode) { + InteractionMode.MOUSE -> { + hoverOffset?.let { offset -> + val matchBox = matchBoxes.firstOrNull { (_, rect) -> + rect.contains(offset) + } - matchBox?.let { (match, rect) -> - drawRect( - color = colorScheme.onSurface, - topLeft = Offset( - x = rect.left, - y = rect.top - ), - size = Size(rect.width, rect.height), - style = Stroke( - width = 1f - ) - ) - - tooltip( - anchorRect = rect.inflate( - delta = 0.8f - ).let { - Rect( - left = offset.x, - top = it.top, - right = offset.x, - bottom = it.bottom + matchBox?.let { (match, rect) -> + drawRect( + color = colorScheme.onSurface, + topLeft = Offset( + x = rect.left, + y = rect.top + ), + size = Size(rect.width, rect.height), + style = Stroke( + width = 1f + ) + ) + + tooltip( + anchorRect = rect.inflate( + delta = 0.8f + ).let { + Rect( + left = offset.x, + top = it.top, + right = offset.x, + bottom = it.bottom + ) + }, + measure = textMeasurer.measure( + text = match.toText(), + style = mergedTextStyle.copy( + color = colorScheme.onSecondaryContainer, + ) + ), + backgroundColor = colorScheme.secondaryContainer, ) - }, - measure = textMeasurer.measure( - text = match.toText(), - style = mergedTextStyle.copy( - color = colorScheme.onSecondaryContainer, + } + } + } + + InteractionMode.TOUCH -> { + val matchBox = matchBoxes.firstOrNull { (match, rect) -> + pressedMatchOffset?.let { offset -> + rect.contains(offset) + } ?: run { + selectedMatch == match + } + } + + matchBox?.let { (_, rect) -> + drawRect( + color = colorScheme.onSurface, + topLeft = Offset( + x = rect.left, + y = rect.top + ), + size = Size(rect.width, rect.height), + style = Stroke( + width = 1f ) - ), - backgroundColor = colorScheme.secondaryContainer, - ) + ) + } } } }, @@ -202,4 +277,38 @@ actual fun TextEditor( VerticalScrollbar(scrollbarAdapter) } + + AnimatedContent( + targetState = selectedMatch, + label = "animated_match_interaction", + transitionSpec = { + val showUp = fadeIn() + slideIntoContainer(SlideDirection.Up) + val hideDown = fadeOut() + slideOutOfContainer(SlideDirection.Down) + + showUp togetherWith hideDown + }, + contentKey = { it != null } + ) { match -> + if (match != null) { + MatchDetails( + match = match, + modifier = Modifier.fillMaxWidth() + ) + } + } +} + +enum class InteractionMode { + MOUSE, + TOUCH; + + companion object { + val Current by lazy { + when { + window.matchMedia("(pointer: coarse)").matches -> TOUCH + window.matchMedia("(pointer: fine)").matches -> MOUSE + else -> error("no support for your device") + } + } + } } \ No newline at end of file From 0092cc42a7871d38e424d113fbe8598eb56e9e30 Mon Sep 17 00:00:00 2001 From: "Irineu A. Silva" Date: Mon, 7 Oct 2024 08:00:01 -0300 Subject: [PATCH 3/3] chore(version): promote v2.1.3 ro release --- build-logic/src/main/kotlin/extension/Project.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-logic/src/main/kotlin/extension/Project.kt b/build-logic/src/main/kotlin/extension/Project.kt index 66040a6f..716f048c 100644 --- a/build-logic/src/main/kotlin/extension/Project.kt +++ b/build-logic/src/main/kotlin/extension/Project.kt @@ -28,7 +28,7 @@ val config = Config( major = 2, minor = 1, patch = 3, - phase = Config.Phase.DEVELOP + phase = Config.Phase.RELEASE ), android = Config.Android( compileSdk = 34,