-
Notifications
You must be signed in to change notification settings - Fork 27
Animate emojis #47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Animate emojis #47
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,7 @@ import androidx.compose.ui.graphics.Color | |
| import androidx.compose.ui.graphics.LinearGradientShader | ||
| import androidx.compose.ui.graphics.Shader | ||
| import androidx.compose.ui.graphics.ShaderBrush | ||
| import androidx.compose.ui.graphics.isSpecified | ||
| import androidx.compose.ui.layout.layout | ||
| import androidx.compose.ui.text.AnnotatedString | ||
| import androidx.compose.ui.text.SpanStyle | ||
|
|
@@ -249,14 +250,68 @@ private fun AnnotatedString.withDynamicColorPhrases( | |
| } | ||
|
|
||
| private fun AnnotatedString.withDynamicColor(color: Color, alpha: () -> Float): AnnotatedString { | ||
| val useDynamicColor = !maybeContainsEmojis() | ||
|
|
||
| val subStyles = spanStyles.map { | ||
| it.copy(item = it.item.copy(brush = DynamicSolidColor(it.item.color, alpha))) | ||
| val style = it.item | ||
| if (useDynamicColor) { | ||
| it.copy(item = style.copy(brush = DynamicSolidColor(style.color) { style.alpha * alpha() })) | ||
| } else if (style.color.isSpecified) { | ||
| it.copy(item = style.copy(color = style.color.copy(alpha = style.color.alpha * alpha()))) | ||
| } else { | ||
| it.copy(item = style.copy(brush = style.brush, alpha = alpha())) | ||
| } | ||
| } | ||
| val fullStyle = | ||
| AnnotatedString.Range(SpanStyle(brush = DynamicSolidColor(color, alpha)), 0, length) | ||
| val fullStyle = AnnotatedString.Range( | ||
| item = if (useDynamicColor) { | ||
| SpanStyle(brush = DynamicSolidColor(color, alpha)) | ||
| } else { | ||
| SpanStyle(brush = DynamicSolidColor(color) { 1f }, alpha = alpha()) | ||
| }, | ||
| start = 0, | ||
| end = length | ||
| ) | ||
| return AnnotatedString(text, subStyles + fullStyle) | ||
| } | ||
|
|
||
| private fun CharSequence.maybeContainsEmojis(): Boolean { | ||
| var i = 0 | ||
| val n = length | ||
| while (i < n) { | ||
| val cp = Character.codePointAt(this, i) | ||
|
|
||
| // --- Quick accepts: common emoji blocks --- | ||
| val isEmoji = when (cp) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. theres seriously no easier 'is emoji'??
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not really unfortunately because it's complicated. There is a regex you can use, but I figures this was faster since it's a best effort only. |
||
| // Misc Symbols + Dingbats + arrows subset that often render as emoji | ||
| in 0x2600..0x27BF -> true | ||
| // Enclosed CJK (e.g., 🈶, 🈚) | ||
| in 0x1F200..0x1F2FF -> true | ||
| // Misc Symbols & Pictographs | ||
| in 0x1F300..0x1F5FF -> true | ||
| // Emoticons | ||
| in 0x1F600..0x1F64F -> true | ||
| // Transport & Map | ||
| in 0x1F680..0x1F6FF -> true | ||
| // Supplemental Symbols & Pictographs | ||
| in 0x1F900..0x1F9FF -> true | ||
| // Symbols & Pictographs Extended-A (newer emoji live here) | ||
| in 0x1FA70..0x1FAFF -> true | ||
| // Regional indicators (flags as pairs, but single is enough for "contains") | ||
| in 0x1F1E6..0x1F1FF -> true | ||
| // Keycap base digits/#/* (paired with VS16 + COMBINING ENCLOSING KEYCAP, but base char is fine) | ||
| in 0x0030..0x0039, 0x0023, 0x002A -> true | ||
| // Variation Selector-16 forces emoji presentation for some BMP symbols | ||
| 0xFE0F -> true | ||
| else -> false | ||
| } | ||
|
|
||
| if (isEmoji) return true | ||
|
|
||
| i += Character.charCount(cp) | ||
| } | ||
| return false | ||
| } | ||
|
|
||
| private fun AnnotatedString.getConsumableAnnotations( | ||
| textFormatObjects: Map<String, Any>, | ||
| offset: Int, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm pretty confused here, why does it use dynamic color in one but..... also in the else block
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, so it's really annoying. The
SpanStylecan either take in a color, or a brush. It also takes in an optional alpha.If you pass a color (or a
SolidColorbrush), the the alpha you pass is multiplied into the color itself. But since Emojis ignore the color (since they can't be tinted) any alpha we pass is completely ignored. So we need to pass aShaderBrushAND an alpha. OurDynamicSolidColorbrush does accept an alpha but it too just multiplies the alpha into the color and thus has the same issue.So we're forced to pass a shader brush and I chose to use
DynamicSolidColorwith an alpha of 1, then pass alpha separately which will set the paint alpha and thus affect emojis.This does have the side effect of recomposing on alpha change but only for paragraphs with emojis so I think it's fine.