From 008a494fcef5c54a50ec6a5c4da3300d9199f470 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 26 Jan 2024 22:25:30 -0800 Subject: [PATCH 1/3] Allow for custom RichSpans --- .../richeditor/model/RichSpanStyle.kt | 4 +--- .../richeditor/model/RichTextConfig.kt | 2 +- .../richeditor/model/RichTextState.kt | 20 +++++++++++++++++++ .../richeditor/model/TextPaddingValues.kt | 2 +- .../mohamedrejeb/richeditor/utils/ListExt.kt | 2 +- .../richeditor/utils/TextLayoutResultExt.kt | 2 +- .../common/components/RichTextStyleRow.kt | 11 +++++++++- 7 files changed, 35 insertions(+), 8 deletions(-) diff --git a/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/model/RichSpanStyle.kt b/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/model/RichSpanStyle.kt index 07fdca3b..bc1cd7bd 100644 --- a/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/model/RichSpanStyle.kt +++ b/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/model/RichSpanStyle.kt @@ -2,7 +2,6 @@ package com.mohamedrejeb.richeditor.model import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.RoundRect -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.Fill @@ -10,13 +9,12 @@ import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.TextRange -import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.sp import com.mohamedrejeb.richeditor.utils.fastForEachIndexed import com.mohamedrejeb.richeditor.utils.getBoundingBoxes -internal interface RichSpanStyle { +interface RichSpanStyle { val spanStyle: (RichTextConfig) -> SpanStyle /** diff --git a/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/model/RichTextConfig.kt b/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/model/RichTextConfig.kt index 2c6dc38c..8e7ff1fe 100644 --- a/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/model/RichTextConfig.kt +++ b/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/model/RichTextConfig.kt @@ -3,7 +3,7 @@ package com.mohamedrejeb.richeditor.model import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextDecoration -internal data class RichTextConfig( +data class RichTextConfig( val linkColor: Color = Color.Blue, val linkTextDecoration: TextDecoration = TextDecoration.Underline, val codeColor: Color = Color.Unspecified, diff --git a/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/model/RichTextState.kt b/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/model/RichTextState.kt index 7f74100f..bba373d1 100644 --- a/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/model/RichTextState.kt +++ b/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/model/RichTextState.kt @@ -320,6 +320,26 @@ class RichTextState internal constructor( handleAddingStyleToSelectedText() } + //RichSpanStyle + + fun addRichSpan(spanStyle: RichSpanStyle) { + if (toRemoveRichSpanStyle::class == spanStyle) + toRemoveRichSpanStyle = RichSpanStyle.Default + toAddRichSpanStyle = spanStyle + + if (!selection.collapsed) + handleAddingStyleToSelectedText() + } + + fun removeRichSpan(spanStyle: RichSpanStyle) { + if (toAddRichSpanStyle::class == spanStyle::class) + toAddRichSpanStyle = RichSpanStyle.Default + toRemoveRichSpanStyle = spanStyle + + if (!selection.collapsed) + handleAddingStyleToSelectedText() + } + /** * Toggle the [ParagraphStyle] * If the passed paragraph style doesn't exist in the [currentParagraphStyle] it's going to be added. diff --git a/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/model/TextPaddingValues.kt b/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/model/TextPaddingValues.kt index b2c02351..9d6c5cb3 100644 --- a/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/model/TextPaddingValues.kt +++ b/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/model/TextPaddingValues.kt @@ -3,7 +3,7 @@ package com.mohamedrejeb.richeditor.model import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.sp -internal data class TextPaddingValues( +data class TextPaddingValues( val horizontal: TextUnit = 0.sp, val vertical: TextUnit = 0.sp ) \ No newline at end of file diff --git a/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/utils/ListExt.kt b/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/utils/ListExt.kt index d21cbfc6..bce37f3b 100644 --- a/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/utils/ListExt.kt +++ b/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/utils/ListExt.kt @@ -16,7 +16,7 @@ internal inline fun List.fastForEach(action: (T) -> Unit) { } @OptIn(ExperimentalContracts::class) -internal inline fun List.fastForEachIndexed(action: (index: Int, T) -> Unit) { +inline fun List.fastForEachIndexed(action: (index: Int, T) -> Unit) { contract { callsInPlace(action) } for (index in indices) { val item = get(index) diff --git a/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/utils/TextLayoutResultExt.kt b/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/utils/TextLayoutResultExt.kt index 515f31d6..928ec75a 100644 --- a/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/utils/TextLayoutResultExt.kt +++ b/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/utils/TextLayoutResultExt.kt @@ -12,7 +12,7 @@ import androidx.compose.ui.text.style.ResolvedTextDirection * entire paragraphs is returned instead of separate lines if [startOffset] * and [endOffset] represent the extreme ends of those paragraph. */ -internal fun TextLayoutResult.getBoundingBoxes( +fun TextLayoutResult.getBoundingBoxes( startOffset: Int, endOffset: Int, flattenForFullParagraphs: Boolean = false diff --git a/sample/common/src/commonMain/kotlin/com/mohamedrejeb/richeditor/sample/common/components/RichTextStyleRow.kt b/sample/common/src/commonMain/kotlin/com/mohamedrejeb/richeditor/sample/common/components/RichTextStyleRow.kt index 453362e0..46a6e682 100644 --- a/sample/common/src/commonMain/kotlin/com/mohamedrejeb/richeditor/sample/common/components/RichTextStyleRow.kt +++ b/sample/common/src/commonMain/kotlin/com/mohamedrejeb/richeditor/sample/common/components/RichTextStyleRow.kt @@ -21,6 +21,7 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.mohamedrejeb.richeditor.model.RichTextState +import com.mohamedrejeb.richeditor.sample.common.richeditor.SpellCheck @Composable fun RichTextStyleRow( @@ -202,7 +203,15 @@ fun RichTextStyleRow( ) } - + item { + RichTextStyleButton( + onClick = { + state.addRichSpan(SpellCheck()) + }, + isSelected = false, + icon = Icons.Outlined.Spellcheck, + ) + } item { Box( From 18982420a24189898c9d0ae453a8e0f971d6eb7f Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 26 Jan 2024 22:31:17 -0800 Subject: [PATCH 2/3] First pass at spellcheck --- .../common/richeditor/SpellCheckSpan.kt | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 sample/common/src/commonMain/kotlin/com/mohamedrejeb/richeditor/sample/common/richeditor/SpellCheckSpan.kt diff --git a/sample/common/src/commonMain/kotlin/com/mohamedrejeb/richeditor/sample/common/richeditor/SpellCheckSpan.kt b/sample/common/src/commonMain/kotlin/com/mohamedrejeb/richeditor/sample/common/richeditor/SpellCheckSpan.kt new file mode 100644 index 00000000..cc56a29a --- /dev/null +++ b/sample/common/src/commonMain/kotlin/com/mohamedrejeb/richeditor/sample/common/richeditor/SpellCheckSpan.kt @@ -0,0 +1,51 @@ +package com.mohamedrejeb.richeditor.sample.common.richeditor + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.unit.dp +import com.mohamedrejeb.richeditor.model.RichSpanStyle +import com.mohamedrejeb.richeditor.model.RichTextConfig +import com.mohamedrejeb.richeditor.utils.fastForEachIndexed +import com.mohamedrejeb.richeditor.utils.getBoundingBoxes + +class SpellCheck: RichSpanStyle { + override val spanStyle: (RichTextConfig) -> SpanStyle = { + SpanStyle() + } + + override fun DrawScope.drawCustomStyle( + layoutResult: TextLayoutResult, + textRange: TextRange, + richTextConfig: RichTextConfig, + topPadding: Float, + startPadding: Float, + ) { + val path = Path() + val strokeColor = Color.Red + val boxes = layoutResult.getBoundingBoxes( + startOffset = textRange.start, + endOffset = textRange.end, + flattenForFullParagraphs = true, + ) + + boxes.fastForEachIndexed { index, box -> + path.moveTo(box.left + startPadding, box.bottom + topPadding) + path.lineTo(box.right + startPadding, box.bottom + topPadding) + + drawPath( + path = path, + color = strokeColor, + style = Stroke( + width = 2.dp.toPx(), + ) + ) + } + } + + override val acceptNewTextInTheEdges: Boolean = false +} From ee263f8752770ec0325c7b03d23c3ecccd6574ce Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 1 Feb 2024 16:32:36 -0800 Subject: [PATCH 3/3] Use object --- .../richeditor/sample/common/components/RichTextStyleRow.kt | 2 +- .../richeditor/sample/common/richeditor/SpellCheckSpan.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sample/common/src/commonMain/kotlin/com/mohamedrejeb/richeditor/sample/common/components/RichTextStyleRow.kt b/sample/common/src/commonMain/kotlin/com/mohamedrejeb/richeditor/sample/common/components/RichTextStyleRow.kt index 46a6e682..523b96f7 100644 --- a/sample/common/src/commonMain/kotlin/com/mohamedrejeb/richeditor/sample/common/components/RichTextStyleRow.kt +++ b/sample/common/src/commonMain/kotlin/com/mohamedrejeb/richeditor/sample/common/components/RichTextStyleRow.kt @@ -206,7 +206,7 @@ fun RichTextStyleRow( item { RichTextStyleButton( onClick = { - state.addRichSpan(SpellCheck()) + state.addRichSpan(SpellCheck) }, isSelected = false, icon = Icons.Outlined.Spellcheck, diff --git a/sample/common/src/commonMain/kotlin/com/mohamedrejeb/richeditor/sample/common/richeditor/SpellCheckSpan.kt b/sample/common/src/commonMain/kotlin/com/mohamedrejeb/richeditor/sample/common/richeditor/SpellCheckSpan.kt index cc56a29a..8ac0d3bb 100644 --- a/sample/common/src/commonMain/kotlin/com/mohamedrejeb/richeditor/sample/common/richeditor/SpellCheckSpan.kt +++ b/sample/common/src/commonMain/kotlin/com/mohamedrejeb/richeditor/sample/common/richeditor/SpellCheckSpan.kt @@ -13,7 +13,7 @@ import com.mohamedrejeb.richeditor.model.RichTextConfig import com.mohamedrejeb.richeditor.utils.fastForEachIndexed import com.mohamedrejeb.richeditor.utils.getBoundingBoxes -class SpellCheck: RichSpanStyle { +object SpellCheck: RichSpanStyle { override val spanStyle: (RichTextConfig) -> SpanStyle = { SpanStyle() }