1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 05:47:05 +09:00

Background span z priority (#436)

This commit is contained in:
Evgenii Kozlov 2020-05-15 18:49:11 +02:00 committed by GitHub
parent 9074251e6d
commit 293bd991d5
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 253 additions and 153 deletions

View file

@ -8,6 +8,8 @@
### Design & UX 🔳
* Updated subtitles for add-block or turn-into bottom sheet items (#429)
* Text background should have the same height as the OS text-selection highlight (#392)
* Text background should have z-axis priority lower as the one of the OS text-selection highlight (#426)
### Fixes & tech 🚒
@ -17,6 +19,7 @@
* `PageViewModel` refactoring (#408)
* Better logging for middleware requests and responses (#421)
* Should persist home dashboard document order (#425)
* New way to render background mark: using `Annotation` span instead of `BackgroundColorSpan` (#436)
### Middleware ⚙️

View file

@ -23,8 +23,8 @@ fun Editable.extractMarks(): List<Mark> = getSpans(0, length, Span::class.java).
is Span.Highlight -> Mark(
range = getSpanStart(span)..getSpanEnd(span),
type = Mark.Type.BACKGROUND_COLOR,
param = span.backgroundColor.let { background ->
ThemeColor.background[background]
param = span.value.let { background ->
ThemeColor.background[background.toInt()]
}
)
is Span.Italic -> Mark(

View file

@ -1,11 +1,9 @@
package com.agileburo.anytype.core_ui.common
import android.graphics.Typeface
import android.os.Parcelable
import android.text.Editable
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.style.*
import com.agileburo.anytype.core_utils.ext.VALUE_ROUNDED
import com.agileburo.anytype.core_utils.ext.removeSpans
import kotlinx.android.parcel.Parcelize
@ -90,7 +88,7 @@ fun Markup.toSpannable() = SpannableStringBuilder(body).apply {
Markup.DEFAULT_SPANNABLE_FLAG
)
Markup.Type.BACKGROUND_COLOR -> setSpan(
Span.Highlight(mark.background()),
Span.Highlight(mark.background().toString()),
mark.from,
mark.to,
Markup.DEFAULT_SPANNABLE_FLAG
@ -148,7 +146,7 @@ fun Editable.setMarkup(markup: Markup) {
Markup.DEFAULT_SPANNABLE_FLAG
)
Markup.Type.BACKGROUND_COLOR -> setSpan(
Span.Highlight(mark.background()),
Span.Highlight(mark.background().toString()),
mark.from,
mark.to,
Markup.DEFAULT_SPANNABLE_FLAG

View file

@ -9,7 +9,6 @@ interface Span {
class Italic : StyleSpan(Typeface.ITALIC), Span
class Strikethrough : StrikethroughSpan(), Span
class TextColor(color: Int) : ForegroundColorSpan(color), Span
class Highlight(color: Int) : BackgroundColorSpan(color), Span
class Url(url: String) : URLSpan(url), Span
class Font(family: String) : TypefaceSpan(family), Span
@ -18,4 +17,10 @@ interface Span {
const val KEYBOARD_KEY = "keyboard"
}
}
class Highlight(color: String) : Annotation(HIGHLIGHT_KEY, color), Span {
companion object {
const val HIGHLIGHT_KEY = "highlight"
}
}
}

View file

@ -10,42 +10,42 @@ import androidx.appcompat.widget.AppCompatEditText
import androidx.core.graphics.withTranslation
import com.agileburo.anytype.core_ui.extensions.toast
import com.agileburo.anytype.core_ui.tools.DefaultTextWatcher
import com.agileburo.anytype.core_ui.widgets.text.highlight.HighlightAttributeReader
import com.agileburo.anytype.core_ui.widgets.text.highlight.HighlightDrawer
import me.saket.bettermovementmethod.BetterLinkMovementMethod
import timber.log.Timber
class TextInputWidget : AppCompatEditText {
private val watchers: MutableList<TextWatcher> = mutableListOf()
private var textRoundedBgHelper: TextRoundedBgHelper? = null
private var highlightDrawer: HighlightDrawer? = null
var selectionDetector: ((IntRange) -> Unit)? = null
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
setupKeyboardMarkupHelpers(context, attrs)
setupHighlightHelpers(context, attrs)
}
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(
context,
attrs,
defStyle
) {
setupKeyboardMarkupHelpers(context, attrs)
}
private fun setupKeyboardMarkupHelpers(
constructor(
context: Context,
attrs: AttributeSet
) {
val attributeReader = TextRoundedBgAttributeReader(context, attrs)
textRoundedBgHelper = TextRoundedBgHelper(
horizontalPadding = attributeReader.horizontalPadding,
verticalPadding = attributeReader.verticalPadding,
drawable = attributeReader.drawable,
drawableLeft = attributeReader.drawableLeft,
drawableMid = attributeReader.drawableMid,
drawableRight = attributeReader.drawableRight
)
attrs: AttributeSet,
defStyle: Int
) : super(context, attrs, defStyle) {
setupHighlightHelpers(context, attrs)
}
private fun setupHighlightHelpers(context: Context, attrs: AttributeSet) {
HighlightAttributeReader(context, attrs).let { reader ->
highlightDrawer = HighlightDrawer(
horizontalPadding = reader.horizontalPadding,
verticalPadding = reader.verticalPadding,
drawable = reader.drawable,
drawableLeft = reader.drawableLeft,
drawableMid = reader.drawableMid,
drawableRight = reader.drawableRight
)
}
}
override fun addTextChangedListener(watcher: TextWatcher) {
@ -91,7 +91,7 @@ class TextInputWidget : AppCompatEditText {
// need to draw bg first so that text can be on top during super.onDraw()
if (text is Spanned && layout != null) {
canvas?.withTranslation(totalPaddingLeft.toFloat(), totalPaddingTop.toFloat()) {
textRoundedBgHelper?.draw(canvas, text as Spanned, layout)
highlightDrawer?.draw(canvas, text as Spanned, layout)
}
}
super.onDraw(canvas)

View file

@ -1,90 +0,0 @@
package com.agileburo.anytype.core_ui.widgets.text
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.text.Annotation
import android.text.Layout
import android.text.Spanned
import com.agileburo.anytype.core_ui.common.Span
import com.agileburo.anytype.core_utils.ext.VALUE_ROUNDED
/**
* Helper class to draw multi-line rounded background to certain parts of a text. The start/end
* positions of the backgrounds are annotated with [android.text.Annotation] class. Each annotation
* should have the annotation key set to **rounded**.
*
* i.e.:
* ```
* <!--without the quotes at the begining and end Android strips the whitespace and also starts
* the annotation at the wrong position-->
* <string name="ltr">"this is <annotation key="rounded">a regular</annotation> paragraph."</string>
* ```
*
* **Note:** BiDi text is not supported.
*
* @param horizontalPadding the padding to be applied to left & right of the background
* @param verticalPadding the padding to be applied to top & bottom of the background
* @param drawable the drawable used to draw the background
* @param drawableLeft the drawable used to draw left edge of the background
* @param drawableMid the drawable used to draw for whole line
* @param drawableRight the drawable used to draw right edge of the background
*/
class TextRoundedBgHelper(
val horizontalPadding: Int,
verticalPadding: Int,
drawable: Drawable,
drawableLeft: Drawable,
drawableMid: Drawable,
drawableRight: Drawable
) {
private val singleLineRenderer: TextRoundedBgRenderer by lazy {
SingleLineRenderer(
horizontalPadding = horizontalPadding,
verticalPadding = verticalPadding,
drawable = drawable
)
}
private val multiLineRenderer: TextRoundedBgRenderer by lazy {
MultiLineRenderer(
horizontalPadding = horizontalPadding,
verticalPadding = verticalPadding,
drawableLeft = drawableLeft,
drawableMid = drawableMid,
drawableRight = drawableRight
)
}
/**
* Call this function during onDraw of another widget such as TextView.
*
* @param canvas Canvas to draw onto
* @param text
* @param layout Layout that contains the text
*/
fun draw(canvas: Canvas, text: Spanned, layout: Layout) {
// ideally the calculations here should be cached since they are not cheap. However, proper
// invalidation of the cache is required whenever anything related to text has changed.
val spans = text.getSpans(0, text.length, Annotation::class.java)
spans.forEach { span ->
if (span.key == Span.Keyboard.KEYBOARD_KEY) {
val spanStart = text.getSpanStart(span)
val spanEnd = text.getSpanEnd(span)
val startLine = layout.getLineForOffset(spanStart)
val endLine = layout.getLineForOffset(spanEnd)
// start can be on the left or on the right depending on the language direction.
val startOffset = (layout.getPrimaryHorizontal(spanStart)
+ -1 * layout.getParagraphDirection(startLine) * horizontalPadding).toInt()
// end can be on the left or on the right depending on the language direction.
val endOffset = (layout.getPrimaryHorizontal(spanEnd)
+ layout.getParagraphDirection(endLine) * horizontalPadding).toInt()
val renderer = if (startLine == endLine) singleLineRenderer else multiLineRenderer
renderer.draw(canvas, layout, startLine, endLine, startOffset, endOffset)
}
}
}
}

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.agileburo.anytype.core_ui.widgets.text
package com.agileburo.anytype.core_ui.widgets.text.highlight
import android.content.Context
import android.graphics.drawable.Drawable
@ -22,17 +22,15 @@ import androidx.core.content.res.getDrawableOrThrow
import com.agileburo.anytype.core_ui.R
/**
* Reads default attributes that [TextRoundedBgHelper] needs from resources. The attributes read
* are:
*
* - chHorizontalPadding: the padding to be applied to left & right of the background
* - chVerticalPadding: the padding to be applied to top & bottom of the background
* - chDrawable: the drawable used to draw the background
* - chDrawableLeft: the drawable used to draw left edge of the background
* - chDrawableMid: the drawable used to draw for whole line
* - chDrawableRight: the drawable used to draw right edge of the background
* Reads default attributes that [HighlightAttributeReader] needs from resources.
* @property horizontalPadding: the padding to be applied to left & right of the background
* @property verticalPadding: the padding to be applied to top & bottom of the background
* @property drawable: the drawable used to draw the background
* @property drawableLeft: the drawable used to draw left edge of the background
* @property drawableMid: the drawable used to draw for whole line
* @property drawableRight: the drawable used to draw right edge of the background
*/
class TextRoundedBgAttributeReader(context: Context, attrs: AttributeSet?) {
class HighlightAttributeReader(context: Context, attrs: AttributeSet?) {
val horizontalPadding: Int
val verticalPadding: Int
@ -44,29 +42,29 @@ class TextRoundedBgAttributeReader(context: Context, attrs: AttributeSet?) {
init {
val typedArray = context.obtainStyledAttributes(
attrs,
R.styleable.TextRoundedBgHelper,
R.styleable.HighlightDrawer,
0,
R.style.RoundedBgTextView
)
horizontalPadding = typedArray.getDimensionPixelSize(
R.styleable.TextRoundedBgHelper_roundedTextHorizontalPadding,
R.styleable.HighlightDrawer_roundedTextHorizontalPadding,
0
)
verticalPadding = typedArray.getDimensionPixelSize(
R.styleable.TextRoundedBgHelper_roundedTextVerticalPadding,
R.styleable.HighlightDrawer_roundedTextVerticalPadding,
0
)
drawable = typedArray.getDrawableOrThrow(
R.styleable.TextRoundedBgHelper_roundedTextDrawable
R.styleable.HighlightDrawer_roundedTextDrawable
)
drawableLeft = typedArray.getDrawableOrThrow(
R.styleable.TextRoundedBgHelper_roundedTextDrawableLeft
R.styleable.HighlightDrawer_roundedTextDrawableLeft
)
drawableMid = typedArray.getDrawableOrThrow(
R.styleable.TextRoundedBgHelper_roundedTextDrawableMid
R.styleable.HighlightDrawer_roundedTextDrawableMid
)
drawableRight = typedArray.getDrawableOrThrow(
R.styleable.TextRoundedBgHelper_roundedTextDrawableRight
R.styleable.HighlightDrawer_roundedTextDrawableRight
)
typedArray.recycle()
}

View file

@ -0,0 +1,179 @@
package com.agileburo.anytype.core_ui.widgets.text.highlight
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.text.Annotation
import android.text.Layout
import android.text.Spanned
import androidx.core.graphics.drawable.DrawableCompat
import com.agileburo.anytype.core_ui.common.Span
import timber.log.Timber
/**
* Helper class to draw multi-line rounded background to certain parts of a text. The start/end
* positions of the backgrounds are annotated with [android.text.Annotation] class. Each annotation
* should have the annotation key set to **rounded**.
*
* i.e.:
* ```
* <!--without the quotes at the begining and end Android strips the whitespace and also starts
* the annotation at the wrong position-->
* <string name="ltr">"this is <annotation key="rounded">a regular</annotation> paragraph."</string>
* ```
*
* **Note:** BiDi text is not supported.
*
* @param horizontalPadding the padding to be applied to left & right of the background
* @param verticalPadding the padding to be applied to top & bottom of the background
* @param drawable the drawable used to draw the background
* @param drawableLeft the drawable used to draw left edge of the background
* @param drawableMid the drawable used to draw for whole line
* @param drawableRight the drawable used to draw right edge of the background
*/
class HighlightDrawer(
val horizontalPadding: Int,
verticalPadding: Int,
drawable: Drawable,
drawableLeft: Drawable,
val drawableMid: Drawable,
drawableRight: Drawable
) {
private val defaultSingleLineRenderer: TextRoundedBgRenderer by lazy {
SingleLineRenderer(
horizontalPadding = 0,
verticalPadding = verticalPadding,
drawable = drawableMid
)
}
private val defaultMultiLineRenderer: TextRoundedBgRenderer by lazy {
MultiLineRenderer(
horizontalPadding = 0,
verticalPadding = verticalPadding,
drawableLeft = drawableMid,
drawableMid = drawableMid,
drawableRight = drawableMid
)
}
private val singleLineHighlightCodeRenderer: TextRoundedBgRenderer by lazy {
SingleLineRenderer(
horizontalPadding = horizontalPadding,
verticalPadding = verticalPadding,
drawable = drawable
)
}
private val multiLineHighlightCodeRenderer: TextRoundedBgRenderer by lazy {
MultiLineRenderer(
horizontalPadding = horizontalPadding,
verticalPadding = verticalPadding,
drawableLeft = drawableLeft,
drawableMid = drawableMid,
drawableRight = drawableRight
)
}
/**
* Call this function during onDraw of another widget such as TextView.
*
* @param canvas Canvas to draw onto
* @param text
* @param layout Layout that contains the text
*/
fun draw(canvas: Canvas, text: Spanned, layout: Layout) {
text.getSpans(0, text.length, Annotation::class.java).forEach { span ->
when (span.key) {
Span.Keyboard.KEYBOARD_KEY -> drawCodeHighlight(
span = span,
text = text,
layout = layout,
canvas = canvas
)
Span.Highlight.HIGHLIGHT_KEY -> drawBackgroundHighlight(
span = span,
text = text,
layout = layout,
canvas = canvas
)
else -> Timber.e("Unexpected span: $span")
}
}
}
private fun drawBackgroundHighlight(
span: Annotation,
text: Spanned,
layout: Layout,
canvas: Canvas
) {
DrawableCompat.wrap(drawableMid).setTint(span.value.toInt())
val spanStart = text.getSpanStart(span)
val spanEnd = text.getSpanEnd(span)
val startLine = layout.getLineForOffset(spanStart)
val endLine = layout.getLineForOffset(spanEnd)
val startOffset = layout.getPrimaryHorizontal(spanStart).toInt()
val endOffset = layout.getPrimaryHorizontal(spanEnd).toInt()
if (startLine == endLine)
defaultSingleLineRenderer.draw(
canvas = canvas,
layout = layout,
startLine = startLine,
endLine = endLine,
startOffset = startOffset,
endOffset = endOffset
)
else
defaultMultiLineRenderer.draw(
canvas = canvas,
layout = layout,
startLine = startLine,
endLine = endLine,
startOffset = startOffset,
endOffset = endOffset
)
}
private fun drawCodeHighlight(
text: Spanned,
span: Annotation,
layout: Layout,
canvas: Canvas
) {
val spanStart = text.getSpanStart(span)
val spanEnd = text.getSpanEnd(span)
val startLine = layout.getLineForOffset(spanStart)
val endLine = layout.getLineForOffset(spanEnd)
// start can be on the left or on the right depending on the language direction.
val startOffset = (layout.getPrimaryHorizontal(spanStart)
+ -1 * layout.getParagraphDirection(startLine) * horizontalPadding).toInt()
// end can be on the left or on the right depending on the language direction.
val endOffset = (layout.getPrimaryHorizontal(spanEnd)
+ layout.getParagraphDirection(endLine) * horizontalPadding).toInt()
if (startLine == endLine)
singleLineHighlightCodeRenderer.draw(
canvas = canvas,
layout = layout,
startLine = startLine,
endLine = endLine,
startOffset = startOffset,
endOffset = endOffset
)
else
multiLineHighlightCodeRenderer.draw(
canvas = canvas,
layout = layout,
startLine = startLine,
endLine = endLine,
startOffset = startOffset,
endOffset = endOffset
)
}
}

View file

@ -14,11 +14,13 @@
* limitations under the License.
*/
package com.agileburo.anytype.core_ui.widgets.text
package com.agileburo.anytype.core_ui.widgets.text.highlight
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.text.Layout
import com.agileburo.anytype.core_ui.widgets.text.getLineBottomWithoutPadding
import com.agileburo.anytype.core_ui.widgets.text.getLineTopWithoutPadding
import kotlin.math.max
import kotlin.math.min

View file

@ -7,7 +7,7 @@
<attr name="active_background_color" format="color" />
</declare-styleable>
<declare-styleable name="TextRoundedBgHelper">
<declare-styleable name="HighlightDrawer">
<attr name="roundedTextHorizontalPadding" format="dimension" />
<attr name="roundedTextVerticalPadding" format="dimension" />
<attr name="roundedTextDrawable" format="reference" />

View file

@ -6,12 +6,12 @@ import android.text.Spanned
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatTextView
import androidx.core.graphics.withTranslation
import com.agileburo.anytype.core_ui.widgets.text.TextRoundedBgAttributeReader
import com.agileburo.anytype.core_ui.widgets.text.TextRoundedBgHelper
import com.agileburo.anytype.core_ui.widgets.text.highlight.HighlightAttributeReader
import com.agileburo.anytype.core_ui.widgets.text.highlight.HighlightDrawer
class RoundedBgTextView : AppCompatTextView {
private val textRoundedBgHelper: TextRoundedBgHelper
private val highlightDrawer: HighlightDrawer
@JvmOverloads
constructor(
@ -19,22 +19,27 @@ class RoundedBgTextView : AppCompatTextView {
attrs: AttributeSet? = null,
defStyleAttr: Int = android.R.attr.textViewStyle
) : super(context, attrs, defStyleAttr) {
val attributeReader = TextRoundedBgAttributeReader(context, attrs)
textRoundedBgHelper = TextRoundedBgHelper(
horizontalPadding = attributeReader.horizontalPadding,
verticalPadding = attributeReader.verticalPadding,
drawable = attributeReader.drawable,
drawableLeft = attributeReader.drawableLeft,
drawableMid = attributeReader.drawableMid,
drawableRight = attributeReader.drawableRight
)
val attributeReader =
HighlightAttributeReader(
context,
attrs
)
highlightDrawer =
HighlightDrawer(
horizontalPadding = attributeReader.horizontalPadding,
verticalPadding = attributeReader.verticalPadding,
drawable = attributeReader.drawable,
drawableLeft = attributeReader.drawableLeft,
drawableMid = attributeReader.drawableMid,
drawableRight = attributeReader.drawableRight
)
}
override fun onDraw(canvas: Canvas) {
// need to draw bg first so that text can be on top during super.onDraw()
if (text is Spanned && layout != null) {
canvas.withTranslation(totalPaddingLeft.toFloat(), totalPaddingTop.toFloat()) {
textRoundedBgHelper.draw(canvas, text as Spanned, layout)
highlightDrawer.draw(canvas, text as Spanned, layout)
}
}
super.onDraw(canvas)