From f13a76790293cbd893964cd7ecd3182428ca92a2 Mon Sep 17 00:00:00 2001 From: Ivan Morgillo Date: Tue, 2 Jul 2024 18:32:35 +0200 Subject: [PATCH] replace deprecated ClickableText * Introduced LinkAnnotation.Clickable to drive the behaviour of links * Replaced SimpleClickableText with the good ol' Text * Successfully wired onUrlClick and onTextClick lambdas and the enabled flag. Closes #418 --- .../DefaultInlineMarkdownRenderer.kt | 20 ++- .../rendering/DefaultMarkdownBlockRenderer.kt | 116 ++++++------------ .../rendering/InlineMarkdownRenderer.kt | 1 + .../view/markdown/MarkdownPreview.kt | 1 + 4 files changed, 56 insertions(+), 82 deletions(-) diff --git a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultInlineMarkdownRenderer.kt b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultInlineMarkdownRenderer.kt index c43009a9e..e93af136e 100644 --- a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultInlineMarkdownRenderer.kt +++ b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultInlineMarkdownRenderer.kt @@ -4,8 +4,7 @@ import androidx.compose.foundation.text.appendInlineContent import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString.Builder -import androidx.compose.ui.text.ExperimentalTextApi -import androidx.compose.ui.text.LinkAnnotation.Url +import androidx.compose.ui.text.LinkAnnotation import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import org.commonmark.renderer.text.TextContentRenderer @@ -29,16 +28,17 @@ public open class DefaultInlineMarkdownRenderer( inlineMarkdown: Iterable, styling: InlinesStyling, enabled: Boolean, + onUrlClicked: ((String) -> Unit)?, ): AnnotatedString = buildAnnotatedString { - appendInlineMarkdownFrom(inlineMarkdown, styling, enabled) + appendInlineMarkdownFrom(inlineMarkdown, styling, enabled, onUrlClicked) } - @OptIn(ExperimentalTextApi::class) private fun Builder.appendInlineMarkdownFrom( inlineMarkdown: Iterable, styling: InlinesStyling, enabled: Boolean, + onUrlClicked: ((String) -> Unit)? = null, ) { for (child in inlineMarkdown) { when (child) { @@ -63,7 +63,17 @@ public open class DefaultInlineMarkdownRenderer( is InlineMarkdown.Link -> { withStyles(styling.link.withEnabled(enabled), child) { - pushLink(Url(it.nativeNode.destination)) + val destination = it.nativeNode.destination + val link = + LinkAnnotation.Clickable( + tag = destination, + linkInteractionListener = { _ -> + if (enabled) { + onUrlClicked?.invoke(destination) + } + }, + ) + pushLink(link) appendInlineMarkdownFrom(it.children, styling, enabled) } } diff --git a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRenderer.kt b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRenderer.kt index 50dffedce..4f0ea7757 100644 --- a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRenderer.kt +++ b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRenderer.kt @@ -6,7 +6,9 @@ import androidx.compose.animation.core.tween import androidx.compose.foundation.HorizontalScrollbar import androidx.compose.foundation.background import androidx.compose.foundation.border +import androidx.compose.foundation.clickable import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -20,7 +22,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollbarAdapter -import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.text.selection.DisableSelection import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -42,7 +43,6 @@ import androidx.compose.ui.input.pointer.pointerHoverIcon import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.layoutId import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.ExperimentalTextApi import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection.Ltr @@ -125,16 +125,29 @@ public open class DefaultMarkdownBlockRenderer( onUrlClick: (String) -> Unit, onTextClick: () -> Unit, ) { - val renderedContent = rememberRenderedContent(block, styling.inlinesStyling, enabled) - SimpleClickableText( + val renderedContent = + rememberRenderedContent( + block, + styling.inlinesStyling, + enabled, + onUrlClick, + ) + val textColor = + styling.inlinesStyling.textStyle.color + .takeOrElse { LocalContentColor.current } + .takeOrElse { styling.inlinesStyling.textStyle.color } + val mergedStyle = styling.inlinesStyling.textStyle.merge(TextStyle(color = textColor)) + val interactionSource = remember { MutableInteractionSource() } + + Text( + modifier = + Modifier.clickable( + interactionSource = interactionSource, + indication = null, + onClick = onTextClick, + ), text = renderedContent, - textStyle = styling.inlinesStyling.textStyle, - color = - styling.inlinesStyling.textStyle.color - .takeOrElse { LocalContentColor.current }, - enabled = enabled, - onUnhandledClick = onTextClick, - onUrlClick = onUrlClick, + style = mergedStyle, ) } @@ -165,7 +178,13 @@ public open class DefaultMarkdownBlockRenderer( onUrlClick: (String) -> Unit, onTextClick: () -> Unit, ) { - val renderedContent = rememberRenderedContent(block, styling.inlinesStyling, enabled) + val renderedContent = + rememberRenderedContent( + block, + styling.inlinesStyling, + enabled, + onUrlClick, + ) Heading( renderedContent, styling.inlinesStyling.textStyle, @@ -173,9 +192,6 @@ public open class DefaultMarkdownBlockRenderer( styling.underlineWidth, styling.underlineColor, styling.underlineGap, - enabled, - onUrlClick, - onTextClick, ) } @@ -187,18 +203,15 @@ public open class DefaultMarkdownBlockRenderer( underlineWidth: Dp, underlineColor: Color, underlineGap: Dp, - enabled: Boolean, - onUrlClick: (String) -> Unit, - onTextClick: () -> Unit, ) { Column(modifier = Modifier.padding(paddingValues)) { - SimpleClickableText( + val textColor = + textStyle.color + .takeOrElse { LocalContentColor.current.takeOrElse { textStyle.color } } + val mergedStyle = textStyle.merge(TextStyle(color = textColor)) + Text( text = renderedContent, - textStyle = textStyle, - color = textStyle.color.takeOrElse { LocalContentColor.current }, - enabled = enabled, - onUnhandledClick = onTextClick, - onUrlClick = onUrlClick, + style = mergedStyle, ) if (underlineWidth > 0.dp && underlineColor.isSpecified) { @@ -457,60 +470,9 @@ public open class DefaultMarkdownBlockRenderer( block: BlockWithInlineMarkdown, styling: InlinesStyling, enabled: Boolean, + onUrlClick: ((String) -> Unit)? = null, ) = remember(block.inlineContent, styling, enabled) { - inlineRenderer.renderAsAnnotatedString(block.inlineContent, styling, enabled) - } - - @OptIn(ExperimentalTextApi::class) - @Composable - private fun SimpleClickableText( - text: AnnotatedString, - textStyle: TextStyle, - enabled: Boolean, - modifier: Modifier = Modifier, - color: Color = Color.Unspecified, - onUrlClick: (String) -> Unit, - onUnhandledClick: () -> Unit, - ) { - var hoverPointerIcon by remember { mutableStateOf(PointerIcon.Default) } - val actualPointerIcon = - remember(hoverPointerIcon, enabled) { - if (enabled) hoverPointerIcon else PointerIcon.Default - } - - val textColor = color.takeOrElse { LocalContentColor.current.takeOrElse { textStyle.color } } - - val mergedStyle = textStyle.merge(TextStyle(color = textColor)) - - ClickableText( - text = text, - style = mergedStyle, - modifier = modifier.pointerHoverIcon(actualPointerIcon, true), - onHover = { offset -> hoverPointerIcon = determinePointerIcon(offset, text) }, - onClick = { offset -> - if (!enabled) return@ClickableText - - val span = text.getUrlAnnotations(offset, offset).firstOrNull() - if (span != null) { - onUrlClick(span.item.url) - } else { - onUnhandledClick() - } - }, - ) - } - - private fun determinePointerIcon( - offset: Int?, - text: AnnotatedString, - ): PointerIcon { - if (offset == null) return PointerIcon.Hand - - val hasLinkAnnotations = text.getLinkAnnotations(offset, offset).isNotEmpty() - return when { - hasLinkAnnotations -> PointerIcon.Hand - else -> PointerIcon.Default - } + inlineRenderer.renderAsAnnotatedString(block.inlineContent, styling, enabled, onUrlClick) } @Composable diff --git a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer.kt b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer.kt index f35a14547..67b7c0ff5 100644 --- a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer.kt +++ b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer.kt @@ -15,6 +15,7 @@ public interface InlineMarkdownRenderer { inlineMarkdown: Iterable, styling: InlinesStyling, enabled: Boolean, + onUrlClicked: ((String) -> Unit)? = null, ): AnnotatedString public companion object { diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownPreview.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownPreview.kt index b218406ab..5db9d4d51 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownPreview.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownPreview.kt @@ -92,6 +92,7 @@ internal fun MarkdownPreview( state = lazyListState, selectable = true, onUrlClick = { url -> Desktop.getDesktop().browse(URI.create(url)) }, + onTextClick = { }, ) VerticalScrollbar(