[go: nahoru, domu]

blob: 2061da274dbd8b5fcbc24c913a702ef2949dab65 [file] [log] [blame]
/*
* Copyright 2020 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.
*/
package androidx.ui.tooling.inspector
import androidx.ui.core.AbsoluteAlignment
import androidx.ui.core.Alignment
import androidx.compose.foundation.Border
import androidx.compose.foundation.shape.corner.CornerBasedShape
import androidx.compose.foundation.shape.corner.CornerSize
import androidx.ui.geometry.Offset
import androidx.ui.geometry.Size
import androidx.ui.graphics.Brush
import androidx.ui.graphics.Color
import androidx.ui.graphics.Shadow
import androidx.ui.graphics.Shape
import androidx.ui.graphics.SolidColor
import androidx.ui.graphics.toArgb
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.intl.LocaleList
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.CrossAxisAlignment
import androidx.compose.foundation.layout.InnerPadding
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontListFontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.font.ResourceFont
import androidx.compose.ui.text.style.BaselineShift
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextGeometricTransform
import androidx.compose.ui.text.style.TextIndent
import androidx.ui.tooling.inspector.ParameterType.DimensionDp
import androidx.ui.unit.Density
import androidx.ui.unit.Dp
import androidx.ui.unit.TextUnit
import androidx.ui.unit.TextUnitType
import java.lang.reflect.Modifier
import kotlin.math.abs
/**
* Factory of [NodeParameter]s.
*
* Each parameter value is converted to a user readable value.
*/
internal class ParameterFactory {
/**
* A map from known values to a user readable string representation.
*/
private val valueLookup = mutableMapOf<Any, String>()
var density = Density(1.0f)
init {
loadFromCompanion(AbsoluteAlignment.Companion)
loadFromCompanion(Alignment.Companion)
loadFromInterface(Arrangement::class.java)
loadFromCompanion(CrossAxisAlignment.Companion)
loadFromCompanion(FontFamily.Companion)
loadFromCompanion(FontWeight.Companion, ignore = "getW")
loadFromCompanion(Shadow.Companion)
loadFromCompanion(TextDecoration.Companion)
loadFromCompanion(TextIndent.Companion)
valueLookup[Color.Unset] = "Unset"
}
/**
* Create a [NodeParameter] from the specified parameter [name] and [value].
*
* Attempt to convert the value to a user readable value.
* For now: return null when a conversion is not possible/found.
*/
fun create(node: MutableInspectorNode, name: String, value: Any?): NodeParameter? {
if (value == null) {
return null
}
val text = valueLookup[value]
if (text != null) {
return NodeParameter(name, ParameterType.String, text)
}
return when (value) {
is AnnotatedString -> NodeParameter(name, ParameterType.String, value.text)
is BaselineShift -> createFromBaselineShift(name, value)
is Boolean -> NodeParameter(name, ParameterType.Boolean, value)
is Border -> createFromBorder(node, name, value)
is Brush -> createFromBrush(name, value)
is Color -> NodeParameter(name, ParameterType.Color, value.toArgb())
is CornerBasedShape -> createFromCornerBasedShape(node, name, value)
is CornerSize -> createFromCornerSize(node, name, value)
is Double -> NodeParameter(name, ParameterType.Double, value)
is Dp -> NodeParameter(name, DimensionDp, value.value)
is Enum<*> -> NodeParameter(name, ParameterType.String, value.toString())
is Float -> NodeParameter(name, ParameterType.Float, value)
is FontListFontFamily -> createFromFontListFamily(name, value)
is FontWeight -> NodeParameter(name, ParameterType.Int32, value.weight)
is InnerPadding -> createFromInnerPadding(node, name, value)
is Int -> NodeParameter(name, ParameterType.Int32, value)
is Locale -> NodeParameter(name, ParameterType.String, value.toString())
is LocaleList -> NodeParameter(name, ParameterType.String,
value.localeList.joinToString())
is Long -> NodeParameter(name, ParameterType.Int64, value)
is Offset -> createFromOffset(name, value)
is Shadow -> createFromShadow(node, name, value)
is Shape -> NodeParameter(name, ParameterType.String, Shape::class.java.simpleName)
is String -> NodeParameter(name, ParameterType.String, value)
is TextGeometricTransform -> createFromTextGeometricTransform(node, name, value)
is TextIndent -> createFromTextIndent(node, name, value)
is TextStyle -> createFromTextStyle(node, name, value)
is TextUnit -> createFromTextUnit(name, value)
else -> null
}
}
private fun createFromBaselineShift(name: String, value: BaselineShift): NodeParameter {
val converted = when (value.multiplier) {
BaselineShift.None.multiplier -> "None"
BaselineShift.Subscript.multiplier -> "Subscript"
BaselineShift.Superscript.multiplier -> "Superscript"
else -> return NodeParameter(name, ParameterType.Float, value.multiplier)
}
return NodeParameter(name, ParameterType.String, converted)
}
private fun createFromBorder(
node: MutableInspectorNode,
name: String,
value: Border
): NodeParameter {
val parameter = NodeParameter(name, ParameterType.String, "Border")
val elements = parameter.elements
create(node, "size", value.size)?.let { elements.add(it) }
create(node, "brush", value.brush)?.let { elements.add(it) }
return parameter
}
private fun createFromBrush(name: String, value: Brush): NodeParameter =
when (value) {
is SolidColor -> NodeParameter(name, ParameterType.Color, value.value.toArgb())
else -> NodeParameter(name, ParameterType.String, classNameOf(value, Brush::class.java))
}
private fun createFromCornerBasedShape(
node: MutableInspectorNode,
name: String,
value: CornerBasedShape
): NodeParameter? {
val parameter = NodeParameter(name, ParameterType.String,
classNameOf(value, CornerBasedShape::class.java))
val elements = parameter.elements
create(node, "topLeft", value.topLeft)?.let { elements.add(it) }
create(node, "topRight", value.topRight)?.let { elements.add(it) }
create(node, "bottomLeft", value.bottomLeft)?.let { elements.add(it) }
create(node, "bottomRight", value.bottomRight)?.let { elements.add(it) }
return parameter
}
private fun createFromCornerSize(
node: MutableInspectorNode,
name: String,
value: CornerSize
): NodeParameter {
val size = Size(node.width.toFloat(), node.height.toFloat())
val pixels = value.toPx(size, density)
return NodeParameter(name, DimensionDp, with (density) { pixels.toDp().value })
}
// For now: select ResourceFontFont closest to W400 and Normal, and return the resId
private fun createFromFontListFamily(name: String, value: FontListFontFamily): NodeParameter? =
findBestResourceFont(value)?.let {
return NodeParameter(name, ParameterType.Resource, it.resId)
}
private fun createFromInnerPadding(
node: MutableInspectorNode,
name: String,
value: InnerPadding
): NodeParameter {
val parameter = NodeParameter(name, ParameterType.String, "InnerPadding")
val elements = parameter.elements
create(node, "start", value.start)?.let { elements.add(it) }
create(node, "end", value.end)?.let { elements.add(it) }
create(node, "top", value.top)?.let { elements.add(it) }
create(node, "bottom", value.bottom)?.let { elements.add(it) }
return parameter
}
private fun createFromOffset(name: String, value: Offset): NodeParameter {
val parameter = NodeParameter(name, ParameterType.String, Offset::class.java.simpleName)
val elements = parameter.elements
elements.add(NodeParameter("x", DimensionDp, with(density) { value.x.toDp().value }))
elements.add(NodeParameter("y", DimensionDp, with(density) { value.y.toDp().value }))
return parameter
}
private fun createFromShadow(
node: MutableInspectorNode,
name: String,
value: Shadow
): NodeParameter {
val parameter = NodeParameter(name, ParameterType.String, Shadow::class.java.simpleName)
val elements = parameter.elements
val blurRadius = with(density) { value.blurRadius.toDp().value }
create(node, "color", value.color)?.let { elements.add(it) }
create(node, "offset", value.offset)?.let { elements.add(it) }
elements.add(NodeParameter("blurRadius", DimensionDp, blurRadius))
return parameter
}
private fun createFromTextGeometricTransform(
node: MutableInspectorNode,
name: String,
value: TextGeometricTransform
): NodeParameter {
val parameter = NodeParameter(name, ParameterType.String,
TextGeometricTransform::class.java.simpleName)
val elements = parameter.elements
create(node, "scaleX", value.scaleX)?.let { elements.add(it) }
create(node, "skewX", value.skewX)?.let { elements.add(it) }
return parameter
}
private fun createFromTextIndent(
node: MutableInspectorNode,
name: String,
value: TextIndent
): NodeParameter {
val parameter = NodeParameter(name, ParameterType.String, TextIndent::class.java.simpleName)
val elements = parameter.elements
create(node, "firstLine", value.firstLine)?.let { elements.add(it) }
create(node, "restLine", value.restLine)?.let { elements.add(it) }
return parameter
}
private fun createFromTextStyle(
node: MutableInspectorNode,
name: String,
value: TextStyle
): NodeParameter {
val parameter = NodeParameter(name, ParameterType.String, TextStyle::class.java.simpleName)
val elements = parameter.elements
create(node, "color", value.color)?.let { elements.add(it) }
create(node, "fontSize", value.fontSize)?.let { elements.add(it) }
create(node, "fontWeight", value.fontWeight)?.let { elements.add(it) }
create(node, "fontStyle", value.fontStyle)?.let { elements.add(it) }
create(node, "fontSynthesis", value.fontSynthesis)?.let { elements.add(it) }
create(node, "fontFamily", value.fontFamily)?.let { elements.add(it) }
create(node, "fontFeatureSettings", value.fontFeatureSettings)?.let { elements.add(it) }
create(node, "letterSpacing", value.letterSpacing)?.let { elements.add(it) }
create(node, "baselineShift", value.baselineShift)?.let { elements.add(it) }
create(node, "textGeometricTransform", value.textGeometricTransform)
?.let { elements.add(it) }
create(node, "localeList", value.localeList)?.let { elements.add(it) }
create(node, "background", value.background)?.let { elements.add(it) }
create(node, "textDecoration", value.textDecoration)?.let { elements.add(it) }
create(node, "shadow", value.shadow)?.let { elements.add(it) }
create(node, "textAlign", value.textAlign)?.let { elements.add(it) }
create(node, "textDirection", value.textDirection)?.let { elements.add(it) }
create(node, "lineHeight", value.lineHeight)?.let { elements.add(it) }
create(node, "textIndent", value.textIndent)?.let { elements.add(it) }
return parameter
}
private fun createFromTextUnit(name: String, value: TextUnit): NodeParameter? =
when (value.type) {
TextUnitType.Sp -> NodeParameter(name, ParameterType.DimensionSp, value.value)
TextUnitType.Em -> NodeParameter(name, ParameterType.DimensionEm, value.value)
TextUnitType.Inherit -> NodeParameter(name, ParameterType.String, "Inherit")
}
private fun classNameOf(value: Any, default: Class<*>): String =
value.javaClass.simpleName.ifEmpty { default.simpleName }
/**
* Select a resource font among the font in the family to represent the font
*
* Prefer the font closest to [FontWeight.Normal] and [FontStyle.Normal]
*/
private fun findBestResourceFont(value: FontListFontFamily): ResourceFont? =
value.fonts.asSequence().filterIsInstance<ResourceFont>().minByOrNull {
abs(it.weight.weight - FontWeight.Normal.weight) + it.style.ordinal
}
private fun loadFromInterface(interfaceClass: Class<*>) {
// REDO: If we decide to add a kotlin reflection dependency
interfaceClass.classes
.flatMap { it.fields.asIterable() }
.filter { it.name == "INSTANCE" }
.associateByTo(valueLookup, { it[null]!! }, { it.declaringClass.simpleName })
}
private fun loadFromCompanion(companionInstance: Any, ignore: String? = null) {
// REDO: If we decide to add a kotlin reflection dependency
companionInstance::class.java.declaredMethods.asSequence()
.filter {
Modifier.isPublic(it.modifiers) &&
it.returnType != Void.TYPE &&
it.parameterTypes.isEmpty() &&
it.name.startsWith("get") &&
(ignore == null || !it.name.startsWith(ignore))
}
.associateByTo(valueLookup, { it(companionInstance)!! }, { it.name.substring(3) })
}
}