| /* |
| * Copyright 2022 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| @file:JvmName("TextDrawStyleKt") |
| |
| package androidx.compose.ui.text.style |
| |
| import androidx.compose.ui.graphics.Brush |
| import androidx.compose.ui.graphics.Color |
| import androidx.compose.ui.graphics.ShaderBrush |
| import androidx.compose.ui.graphics.SolidColor |
| import androidx.compose.ui.graphics.isSpecified |
| import androidx.compose.ui.graphics.lerp as lerpColor |
| import androidx.compose.ui.text.lerpDiscrete |
| import androidx.compose.ui.util.lerp |
| |
| /** |
| * An internal interface to represent possible ways to draw Text e.g. color, brush. This interface |
| * aims to unify unspecified versions of complementary drawing styles. There are some guarantees |
| * as following; |
| * |
| * - If [color] is not [Color.Unspecified], brush is null. |
| * - If [brush] is not null, color is [Color.Unspecified]. |
| * - Both [color] can be [Color.Unspecified] and [brush] null, indicating that nothing is specified. |
| * - [SolidColor] brushes are stored as regular [Color]. |
| */ |
| internal interface TextForegroundStyle { |
| val color: Color |
| |
| val brush: Brush? |
| |
| val alpha: Float |
| |
| fun merge(other: TextForegroundStyle): TextForegroundStyle { |
| // This control prevents Color or Unspecified TextForegroundStyle to override an existing |
| // Brush. It is a temporary measure to prevent Material Text composables to remove given |
| // Brush from a TextStyle. |
| // TODO(b/230787077): Just return other.takeOrElse { this } when Brush is stable. |
| return when { |
| other is BrushStyle && this is BrushStyle -> |
| BrushStyle(other.value, other.alpha.takeOrElse { this.alpha }) |
| other is BrushStyle && this !is BrushStyle -> other |
| other !is BrushStyle && this is BrushStyle -> this |
| else -> other.takeOrElse { this } |
| } |
| } |
| |
| fun takeOrElse(other: () -> TextForegroundStyle): TextForegroundStyle { |
| return if (this != Unspecified) this else other() |
| } |
| |
| object Unspecified : TextForegroundStyle { |
| override val color: Color |
| get() = Color.Unspecified |
| |
| override val brush: Brush? |
| get() = null |
| |
| override val alpha: Float |
| get() = Float.NaN |
| } |
| |
| companion object { |
| fun from(color: Color): TextForegroundStyle { |
| return if (color.isSpecified) ColorStyle(color) else Unspecified |
| } |
| |
| fun from(brush: Brush?, alpha: Float): TextForegroundStyle { |
| return when (brush) { |
| null -> Unspecified |
| is SolidColor -> from(brush.value.modulate(alpha)) |
| is ShaderBrush -> BrushStyle(brush, alpha) |
| } |
| } |
| } |
| } |
| |
| private data class ColorStyle( |
| val value: Color |
| ) : TextForegroundStyle { |
| init { |
| require(value.isSpecified) { |
| "ColorStyle value must be specified, use TextForegroundStyle.Unspecified instead." |
| } |
| } |
| |
| override val color: Color |
| get() = value |
| |
| override val brush: Brush? |
| get() = null |
| |
| override val alpha: Float |
| get() = color.alpha |
| } |
| |
| private data class BrushStyle( |
| val value: ShaderBrush, |
| override val alpha: Float |
| ) : TextForegroundStyle { |
| override val color: Color |
| get() = Color.Unspecified |
| |
| override val brush: Brush |
| get() = value |
| } |
| |
| /** |
| * If both TextForegroundStyles do not represent a Brush, lerp the color values. Otherwise, lerp |
| * start to end discretely. |
| */ |
| internal fun lerp( |
| start: TextForegroundStyle, |
| stop: TextForegroundStyle, |
| fraction: Float |
| ): TextForegroundStyle { |
| return if ((start !is BrushStyle && stop !is BrushStyle)) { |
| TextForegroundStyle.from(lerpColor(start.color, stop.color, fraction)) |
| } else if (start is BrushStyle && stop is BrushStyle) { |
| TextForegroundStyle.from( |
| lerpDiscrete(start.brush, stop.brush, fraction), |
| lerp(start.alpha, stop.alpha, fraction) |
| ) |
| } else { |
| lerpDiscrete(start, stop, fraction) |
| } |
| } |
| |
| internal fun Color.modulate(alpha: Float): Color = when { |
| alpha.isNaN() || alpha >= 1f -> this |
| else -> this.copy(alpha = this.alpha * alpha) |
| } |
| |
| private fun Float.takeOrElse(block: () -> Float): Float { |
| return if (this.isNaN()) block() else this |
| } |